{
  "openapi": "3.1.0",
  "info": {
    "title": "Vitrina API",
    "version": "11.13.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"
        ]
      },
      "TenantSettingsPublic": {
        "type": "object",
        "properties": {
          "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."
          },
          "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."
              },
              "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."
          }
        }
      },
      "TenantSettingsPublicWrite": {
        "type": "object",
        "properties": {
          "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"
                }
              }
            },
            "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). 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`."
          }
        }
      },
      "PaymentMethodSetup": {
        "type": "object",
        "properties": {
          "provider": {
            "type": "string",
            "enum": [
              "fintoc",
              "mercadopago"
            ]
          },
          "status": {
            "type": "string",
            "enum": [
              "pending",
              "active"
            ]
          },
          "mandateId": {
            "type": "string"
          },
          "setupUrl": {
            "type": [
              "string",
              "null"
            ],
            "format": "uri"
          }
        },
        "required": [
          "provider",
          "status",
          "mandateId",
          "setupUrl"
        ]
      },
      "PaymentMethodStatus": {
        "type": "object",
        "properties": {
          "provider": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "fintoc",
              "mercadopago"
            ]
          },
          "status": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "pending",
              "active",
              "revoked",
              "failed"
            ]
          },
          "mandateId": {
            "type": [
              "string",
              "null"
            ]
          }
        },
        "required": [
          "provider",
          "status",
          "mandateId"
        ]
      },
      "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"
            ]
          },
          "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",
          "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"
            ]
          },
          "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"
            ]
          },
          "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",
          "duration_minutes",
          "vendor_duration_minutes",
          "review_state",
          "reviewed_at",
          "reviewed_by",
          "is_telehealth",
          "online_bookable",
          "is_entry",
          "requires_consent_template_id",
          "eligibility_min_age_years",
          "eligibility_max_age_years",
          "eligibility_sexo",
          "eligibility_previsiones",
          "active",
          "synced_at"
        ]
      },
      "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",
              "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",
              "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",
              "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",
                "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",
                "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",
                "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"
        ]
      },
      "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"
        ]
      },
      "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"
            ]
          }
        },
        "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"
        ]
      },
      "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"
            ]
          },
          "destination": {
            "type": "object",
            "properties": {}
          },
          "privacy": {
            "$ref": "#/components/schemas/AdsPrivacyCheck"
          }
        },
        "required": [
          "action",
          "destination",
          "privacy"
        ]
      },
      "AdsRuleSyncResult": {
        "type": "object",
        "properties": {
          "vertical": {
            "type": [
              "string",
              "null"
            ]
          },
          "rules": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "stage": {
                  "type": "string",
                  "enum": [
                    "lead_created",
                    "appointment_booked",
                    "appointment_attended",
                    "quote_presented",
                    "closed_won",
                    "payment_received"
                  ]
                },
                "action": {
                  "type": "string",
                  "enum": [
                    "created",
                    "updated",
                    "skipped"
                  ]
                },
                "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": "Atribu's per-rule × destination verdicts and remediation, verbatim."
          },
          "checked_at": {
            "type": [
              "string",
              "null"
            ]
          },
          "stale": {
            "type": "boolean"
          },
          "ttl_ms": {
            "type": "number"
          },
          "privacy": {
            "$ref": "#/components/schemas/AdsPrivacyCheck"
          }
        },
        "required": [
          "results",
          "checked_at",
          "stale",
          "ttl_ms"
        ]
      },
      "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"
            ]
          },
          "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"
                  ]
                }
              },
              "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"
                          ]
                        },
                        "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"
        ]
      },
      "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"
          },
          "atribu_connection_id": {
            "type": "string"
          }
        },
        "required": [
          "external_account_id",
          "name",
          "currency",
          "timezone",
          "delivers_spend",
          "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"
            ]
          },
          "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",
          "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"
        ]
      },
      "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"
          }
        ]
      },
      "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."
          }
        },
        "required": [
          "date",
          "spend",
          "revenue",
          "outcomes",
          "attributed_outcomes",
          "partial"
        ]
      },
      "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"
              },
              "cash_payments": {
                "type": "integer"
              },
              "first_payment_revenue": {
                "type": "number"
              },
              "first_payment_customers": {
                "type": "integer"
              },
              "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"
                }
              }
            },
            "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"
              },
              "cash_payments": {
                "type": "integer"
              },
              "first_payment_revenue": {
                "type": "number"
              },
              "first_payment_customers": {
                "type": "integer"
              },
              "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"
                }
              }
            },
            "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."
          }
        },
        "required": [
          "current",
          "previous"
        ]
      },
      "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"
                },
                "outcome_value": {
                  "type": "number"
                },
                "roas": {
                  "type": "number"
                },
                "direct_cash_value": {
                  "type": [
                    "number",
                    "null"
                  ]
                },
                "inherited_cash_value": {
                  "type": [
                    "number",
                    "null"
                  ]
                },
                "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"
                      ]
                    }
                  },
                  "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`."
                }
              },
              "required": [
                "campaign_id",
                "campaign_name",
                "campaign_external_id",
                "spend",
                "outcome_count",
                "outcome_value",
                "roas",
                "direct_cash_value",
                "inherited_cash_value",
                "top_ad"
              ]
            }
          }
        },
        "required": [
          "data"
        ]
      },
      "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"
              ]
            }
          }
        },
        "required": [
          "data"
        ]
      },
      "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"
                  ]
                },
                "score_window": {
                  "type": "string"
                },
                "composite_score": {
                  "type": [
                    "number",
                    "null"
                  ]
                },
                "top_performer_likelihood": {
                  "type": [
                    "number",
                    "null"
                  ]
                },
                "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"
                  ]
                },
                "fatigue_risk_tier": {
                  "type": [
                    "string",
                    "null"
                  ],
                  "enum": [
                    "low",
                    "medium",
                    "high",
                    "critical"
                  ]
                },
                "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."
                }
              },
              "required": [
                "ad_external_id",
                "ad_name",
                "campaign_name",
                "campaign",
                "ad_set_name",
                "creative_thumbnail_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"
              ]
            }
          }
        },
        "required": [
          "data"
        ]
      },
      "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"
              },
              "instrumentation": {
                "type": "object",
                "properties": {
                  "score": {
                    "type": "number"
                  },
                  "verdict": {
                    "type": "string",
                    "enum": [
                      "healthy_tracking",
                      "broken_tracking"
                    ]
                  },
                  "tracking_healthy": {
                    "type": "boolean"
                  },
                  "coverage_interpretation": {
                    "type": "string"
                  },
                  "missing": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    }
                  }
                },
                "required": [
                  "score",
                  "verdict",
                  "tracking_healthy",
                  "coverage_interpretation",
                  "missing"
                ]
              },
              "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"
                  }
                },
                "required": [
                  "recent_roas",
                  "prior_roas",
                  "recent_spend",
                  "recent_traceable_revenue",
                  "days_with_cash"
                ]
              }
            },
            "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)."
          }
        },
        "required": [
          "trust",
          "utm",
          "attribution_coverage",
          "outcomes_coverage"
        ]
      },
      "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."
          },
          "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": {
                "type": "string",
                "description": "A suggested «Pregúntale» question this line invites."
              },
              "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": {
                  "type": "string",
                  "description": "A suggested «Pregúntale» question this line invites."
                }
              },
              "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"
            ],
            "description": "Who wrote the sentences — both read from the same pinned facts."
          },
          "generated_at": {
            "type": "string"
          },
          "vocabulary": {
            "type": "string",
            "enum": [
              "automotive",
              "healthcare",
              "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"
        ]
      },
      "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",
              "budget_change",
              "adset_duplicate_swap",
              "utm_refresh",
              "wiring_recheck",
              "nav"
            ]
          },
          "screen": {
            "type": "array",
            "items": {
              "type": "string",
              "enum": [
                "resumen",
                "campanas",
                "creativos",
                "salud"
              ]
            }
          },
          "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"
                ]
              }
            },
            "required": [
              "level",
              "external_id",
              "name"
            ]
          },
          "params": {
            "type": "object",
            "additionalProperties": {},
            "description": "The engine’s own vocabulary, e.g. `{ budget_change_pct: 20 }`."
          },
          "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",
                  "wiring.ad_sets",
                  "quality.utm_refresh",
                  "wiring.recheck",
                  "actions.rollback"
                ]
              },
              "recommendation_id": {
                "type": "string"
              }
            },
            "required": [
              "op"
            ],
            "description": "Which operation executes it; `null` for `nav`."
          },
          "href": {
            "type": "string",
            "description": "`nav` only: a workspace-relative path."
          },
          "cta": {
            "type": "string"
          },
          "facts": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Fact keys the card is built from."
          },
          "dismissed_until": {
            "type": [
              "string",
              "null"
            ]
          }
        },
        "required": [
          "key",
          "kind",
          "screen",
          "primary",
          "title",
          "why",
          "effect",
          "target",
          "params",
          "reversible",
          "risk",
          "engine_ref",
          "facts",
          "dismissed_until"
        ]
      },
      "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"
                }
              },
              "required": [
                "key",
                "label",
                "before",
                "after"
              ]
            }
          },
          "warnings": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "consent": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Duplicate-and-swap only: `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": "Duplicate-and-swap only: what the owner accepts before confirming. Execute must send `consent: { version, text_hash }`."
          },
          "reversible": {
            "type": "boolean"
          },
          "engine_op": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "recommendations.apply",
              "ads.pause",
              "wiring.ad_sets",
              "quality.utm_refresh",
              "wiring.recheck",
              "actions.rollback"
            ]
          }
        },
        "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",
              "budget_change",
              "adset_duplicate_swap",
              "utm_refresh",
              "wiring_recheck"
            ]
          },
          "status": {
            "type": "string",
            "enum": [
              "queued",
              "running",
              "succeeded",
              "failed",
              "refused",
              "rolled_back"
            ]
          },
          "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"
            ]
          },
          "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",
          "outcome",
          "error",
          "rollback_available",
          "rollback_of",
          "simulated",
          "created_at",
          "finished_at"
        ]
      },
      "AdsActionExecutionListItem": {
        "allOf": [
          {
            "$ref": "#/components/schemas/AdsActionExecution"
          },
          {
            "type": "object",
            "properties": {
              "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"
                    ]
                  }
                },
                "required": [
                  "level",
                  "external_id",
                  "name"
                ]
              },
              "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": [
              "target",
              "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"
          },
          "last_seen_at": {
            "type": [
              "string",
              "null"
            ]
          },
          "events_24h": {
            "type": [
              "integer",
              "null"
            ],
            "minimum": 0
          },
          "domain": {
            "type": [
              "string",
              "null"
            ]
          },
          "source": {
            "type": "string",
            "enum": [
              "tracker",
              "health"
            ]
          }
        },
        "required": [
          "installed",
          "last_seen_at",
          "events_24h",
          "domain",
          "source"
        ],
        "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."
      },
      "AdsFeedItem": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "stage": {
            "type": "string",
            "enum": [
              "lead_created",
              "appointment_booked",
              "appointment_attended",
              "quote_presented",
              "closed_won",
              "payment_received"
            ]
          },
          "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. `true` with `ad: null` = «anuncio por confirmar»."
          },
          "value": {
            "type": [
              "number",
              "null"
            ],
            "description": "CLP for money stages; `null` otherwise (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"
              },
              "display_name": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "Only when the caller holds `contacts:read`; else `null`."
              },
              "key": {
                "type": "string",
                "description": "Last 4 hex of the contact id."
              }
            },
            "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"
            ]
          }
        },
        "required": [
          "id",
          "stage",
          "occurred_at",
          "created_at",
          "matched",
          "value",
          "action_source",
          "contact",
          "ad",
          "credited_weight",
          "touch_count"
        ]
      },
      "AdsJourney": {
        "type": "object",
        "properties": {
          "steps": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "kind": {
                  "type": "string",
                  "enum": [
                    "ad",
                    "click",
                    "visit",
                    "chat",
                    "outcome"
                  ]
                },
                "label": {
                  "type": "string"
                },
                "detail": {
                  "type": [
                    "string",
                    "null"
                  ]
                },
                "at": {
                  "type": "string"
                },
                "channel": {
                  "type": "string"
                }
              },
              "required": [
                "kind",
                "label",
                "detail",
                "at"
              ]
            }
          },
          "touch_count": {
            "type": "integer"
          },
          "credited_weight": {
            "type": [
              "number",
              "null"
            ]
          },
          "model": {
            "type": "string"
          },
          "source": {
            "type": "string",
            "enum": [
              "engine",
              "vitrina"
            ],
            "description": "`engine` = the attribution engine’s touches; `vitrina` = only the outcomes Vitrina recorded for this contact (the delegated key cannot read people, or the engine has no path for it yet)."
          }
        },
        "required": [
          "steps",
          "touch_count",
          "credited_weight",
          "model",
          "source"
        ]
      }
    },
    "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,
                      "model": "xiaomi/mimo-v2.6-flash",
                      "temperature": null,
                      "default_max_steps": 5,
                      "reasoning_effort": "high",
                      "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_model": 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,
                      "model": "xiaomi/mimo-v2.6-flash",
                      "temperature": null,
                      "default_max_steps": 5,
                      "reasoning_effort": "high",
                      "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_model": 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. `model` is an escape hatch and almost always omitted — Vitrina picks and manages the runtime model (`ai-agent.service` `DEFAULT_AGENT_MODEL`, currently a flash-tier model; the product never runs a \"pro\"/reasoning-max tier here) and the UI never asks for one. 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.",
        "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": ""
                  },
                  "model": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 120
                  }
                },
                "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,
                    "model": "xiaomi/mimo-v2.6-flash",
                    "temperature": null,
                    "default_max_steps": 5,
                    "reasoning_effort": "high",
                    "autonomy_level": "supervised",
                    "system_prompt": "",
                    "knowledge_tags": [],
                    "tags": [],
                    "followups_enabled": true,
                    "allowed_url_prefixes": [],
                    "published_version_id": null,
                    "draft_system_prompt": null,
                    "draft_model": 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",
        "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.",
        "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,
                    "model": "xiaomi/mimo-v2.6-flash",
                    "temperature": null,
                    "default_max_steps": 5,
                    "reasoning_effort": "high",
                    "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_model": 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"
      },
      "put": {
        "summary": "Update AI agent",
        "description": "Every field optional. Unlike the system prompt and the tool/skill/KB wiring, `name`/`description`/`model`/`followups_enabled`/`allowed_url_prefixes` are LIVE IMMEDIATELY — they do not stage on the draft and do not need a publish. A `model` sent here must stay inside the platform’s flash-tier catalogue; it is not a free-form passthrough to whatever OpenRouter exposes.",
        "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
                  },
                  "model": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 120
                  },
                  "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,
                    "model": "xiaomi/mimo-v2.6-flash",
                    "temperature": null,
                    "default_max_steps": 5,
                    "reasoning_effort": "high",
                    "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_model": 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.",
        "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
      }
    },
    "/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`).",
        "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 system prompt",
        "description": "A convenience shortcut for editing ONLY the prompt: identical in effect to `PUT /ai-agents/{id}/draft` with just `system_prompt` set — it STAGES the change onto `draft_system_prompt` and does not touch the live runtime until `POST /ai-agents/{id}/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": {
                  "system_prompt": {
                    "type": "string"
                  },
                  "knowledge_tags": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "minLength": 1
                    }
                  }
                },
                "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,
                    "model": "xiaomi/mimo-v2.6-flash",
                    "temperature": null,
                    "default_max_steps": 5,
                    "reasoning_effort": "high",
                    "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_model": 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"
                            }
                          },
                          "model": {
                            "type": [
                              "string",
                              "null"
                            ]
                          },
                          "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",
                          "model",
                          "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"
                          }
                        },
                        "model": {
                          "type": [
                            "string",
                            "null"
                          ]
                        },
                        "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",
                        "model",
                        "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"
                          }
                        },
                        "model": {
                          "type": [
                            "string",
                            "null"
                          ]
                        },
                        "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",
                        "model",
                        "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.",
        "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`, `model`/`temperature`/`max_steps`, `tool_wiring`, `skill_ids`, `kb_file_ids`, `knowledge_tags`. 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`.",
        "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
                    }
                  },
                  "model": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 120
                  },
                  "temperature": {
                    "type": [
                      "number",
                      "null"
                    ],
                    "minimum": 0,
                    "maximum": 2
                  },
                  "reasoning_effort": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "enum": [
                      "max",
                      "xhigh",
                      "high",
                      "medium",
                      "low",
                      "minimal",
                      "none"
                    ]
                  },
                  "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
                  }
                }
              },
              "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}/publish": {
      "post": {
        "summary": "Atomically promote the draft onto the live runtime + snapshot a version",
        "description": "Gated by Agent Evals (ADR 0098): when the agent has an ENABLED GOLDEN suite whose gate is `blocked` or `stale`, this answers **409** `{ error, code: 'evals_blocked' | 'evals_stale', gate }` — see `GET /ai-agents/{id}/publish-gate` for the semantics. `force: true` publishes anyway and is recorded in the audit log with the actor. `skip_eval: true` skips the post-publish suite/scenario run; it does NOT bypass the gate.",
        "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"
                  }
                },
                "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,
                    "model": "xiaomi/mimo-v2.6-flash",
                    "temperature": null,
                    "default_max_steps": 5,
                    "reasoning_effort": "high",
                    "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_model": 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.",
        "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`.",
        "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"
                    ]
                  }
                }
              },
              "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
      }
    },
    "/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,
                      "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,
                      "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).",
        "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
                  }
                },
                "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,
                    "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`), and a workspace may apply any of them. `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"
              ]
            },
            "required": false,
            "name": "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). 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"
                    },
                    "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"
                        ]
                      },
                      {
                        "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"
                        ]
                      }
                    ]
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "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,
                      "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",
                        "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",
                        "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",
                        "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}": {
      "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,
                    "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",
                        "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",
                        "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",
                        "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).",
        "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"
                  }
                }
              },
              "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,
                    "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
      }
    },
    "/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",
                      "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",
                      "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",
                      "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.",
        "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
                    }
                  },
                  "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",
                      "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",
                      "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"
                }
              }
            }
          },
          "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.",
        "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
                  }
                },
                "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",
                    "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",
        "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
                    }
                  },
                  "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",
                      "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",
                      "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"
                }
              }
            }
          },
          "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",
                    "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.",
        "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
                  }
                },
                "additionalProperties": false
              },
              "example": {
                "position": 2,
                "sla_days": 5
              }
            }
          }
        },
        "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",
                    "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"
      },
      "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",
                        "audit:read",
                        "analytics:read",
                        "analytics:write",
                        "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",
                        "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",
                        "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",
                        "price_approval:read",
                        "price_approval:request",
                        "price_approval:approve",
                        "legal:read",
                        "legal:write"
                      ]
                    },
                    "minItems": 1,
                    "maxItems": 32
                  },
                  "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/token-usage": {
      "get": {
        "summary": "Daily token usage by model (prompt + completion + total)",
        "description": "One row per (day, model) over `from`–`to`: `prompt_tokens`, `completion_tokens`, `total_tokens`. `model` is the OpenRouter model id the platform actually ran — every model this workspace’s agents run is flash-tier; the platform never routes a turn to a \"pro\"/reasoning-max model.",
        "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": "Token usage rows",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": [
                    {
                      "bucket": "2026-09-22",
                      "model": "xiaomi/mimo-v2.6-flash",
                      "prompt_tokens": 812000,
                      "completion_tokens": 96000,
                      "total_tokens": 908000
                    },
                    {
                      "bucket": "2026-09-22",
                      "model": "google/gemini-2.5-flash",
                      "prompt_tokens": 220000,
                      "completion_tokens": 31000,
                      "total_tokens": 251000
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "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/Atribu/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_ai_agent_id": null,
                      "help_center_id": null,
                      "public_number_id": 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_ai_agent_id": null,
                    "help_center_id": null,
                    "public_number_id": 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
      }
    },
    "/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",
                        "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",
                        "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",
                        "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.",
        "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"
                      ],
                      "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": "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"
                    ]
                  },
                  "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"
                    ],
                    "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**).",
        "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"
                    ]
                  },
                  "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
                  }
                }
              },
              "example": {
                "custom_role_id": "d2d2d2d2-0000-4000-8000-000000000001",
                "location_ids": [
                  "b1b1b1b1-0000-4000-8000-000000000001"
                ]
              }
            }
          }
        },
        "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"
                    ],
                    "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/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"
                    ]
                  },
                  "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/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/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 Atribu, 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. `?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": "ok",
                      "checked_at": "2026-09-15T18:00:00.000Z",
                      "since": "2026-09-01T00: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/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"
      }
    },
    "/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`. A connected app receives exactly those keys; the workspace's own credentials may see further workspace-configuration keys, which are not contract and may change without notice.",
        "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"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "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. 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`. A connected app receives exactly those keys; the workspace's own credentials may see further workspace-configuration keys, which are not contract and may change without notice.",
        "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"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "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.",
        "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"
                              }
                            },
                            "required": [
                              "key",
                              "access"
                            ]
                          }
                        },
                        "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": {}
                              },
                              "created_at": {
                                "type": "string"
                              },
                              "updated_at": {
                                "type": "string"
                              }
                            },
                            "required": [
                              "tenant_id",
                              "feature",
                              "status",
                              "source",
                              "granted_at",
                              "cancel_at_period_end",
                              "canceled_at",
                              "metadata",
                              "created_at",
                              "updated_at"
                            ]
                          }
                        },
                        "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"
                          ]
                        }
                      },
                      "required": [
                        "catalog",
                        "entitlements",
                        "usage"
                      ]
                    }
                  },
                  "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": {},
                        "created_at": "2026-09-01T12:00:00.000Z",
                        "updated_at": "2026-09-01T12:00:00.000Z"
                      }
                    ],
                    "usage": {
                      "tasador": {
                        "used": 3,
                        "free_allowance": 10,
                        "period": "2026-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"
      }
    },
    "/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. 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": {},
                      "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 canonical vehicle brands (autocomplete)",
        "description": "Distinct canonical brands unioned from vehicle_catalog + vehicle_taxonomy, reconciled to the ML_BRANDS spelling. Global reference data (no tenant scope); cacheable.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Vehicles"
        ],
        "responses": {
          "200": {
            "description": "Brands",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": [
                    "Toyota",
                    "Chevrolet",
                    "Nissan",
                    "Suzuki"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "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 canonical models for a brand (autocomplete)",
        "description": "Distinct canonical models for `brand`, same reference data as `/catalog/brands`.",
        "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": [
                    "Corolla",
                    "Yaris",
                    "RAV4"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "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 versions/trims for a brand+model (autocomplete suggestions)",
        "description": "Distinct canonical versions/trims for `brand` + `model`.",
        "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": [
                    "1.8 XEI CVT",
                    "2.0 SEG 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"
      }
    },
    "/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": {
      "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\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.",
        "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": "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": "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"
              ]
            },
            "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",
                "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,
                      "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,
                      "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). A plate the Chilean grammar cannot read derives plate_normalized = null, so the DB backstop cannot see it: the service then compares the RAW value (upper/trim) against the tenant active stock and answers the SAME 409, and flags the unit under attributes.curated.needsReview = [\"plate\"] with curated.plateReason = \"plate_unrecognized\". The plate is never refused on its own merits (allow-and-flag, ADR 0022) — only a duplicate is.\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` and Facebook Marketplace, `already_published`, or `error` with the reason). `publications` is absent when `publish_to` was not sent.",
        "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"
                  },
                  "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
                        }
                      },
                      "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
                  },
                  "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`, `facebook`, `facebook_marketplace`, `linze`, `mercadolibre`, `yapo`) 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": "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,
                    "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,
                    "not_on_lot": false,
                    "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,
                          "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",
                          "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",
        "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"
              ]
            },
            "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": {
              "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",
                "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.",
        "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": "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,
                    "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,
                    "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"
      },
      "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).\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.",
        "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"
                  },
                  "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
                        }
                      },
                      "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
                  },
                  "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`, `facebook`, `facebook_marketplace`, `linze`, `mercadolibre`, `yapo`) 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": {
                "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": "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,
                    "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,
                    "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"
      },
      "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. Existing publications are NOT closed by this call — close ads first (`POST /vehicles/{id}/close-ads`) if the vehicle is live on any portal.",
        "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,
                    "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,
                    "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,
                    "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,
                    "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. 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-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.",
        "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",
                      "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 is asynchronous and has no HTTP API at all.** There is no portal to call: `publish()` inserts a `device_task` into a queue a real Chrome extension — running in the salesperson’s own logged-in profile — polls and executes at a human pace. This call answers `status: \"pending\"` the instant that task is enqueued, honestly — never a fabricated `published`. `external_id` in that case is the PROVISIONAL `fbm-task:<taskId>` handle, not a real Facebook item id, and the publication carries no `permalink` yet. **The task may never complete** — the extension can be offline, paused or blocked by a Facebook checkpoint. Poll `GET /vehicles/{id}/publications` for the row to turn `published` (with a real `external_id`/`permalink`) or `error`, or subscribe to `vehicle.published` / `vehicle.publish_failed`.\n\n**Chileautos (Panel) (`chileautos_panel`) is asynchronous too.** 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",
                        "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": "facebook_marketplace",
                      "status": "pending",
                      "external_id": "fbm-task:8f1c4d2e-5a6b-4c7d-8e9f-0a1b2c3d4e5f",
                      "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": "facebook_marketplace",
                        "external_id": "fbm-task:8f1c4d2e-5a6b-4c7d-8e9f-0a1b2c3d4e5f",
                        "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,
                        "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",
                    "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. 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"
                  },
                  "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"
                    ]
                  }
                },
                "required": [
                  "message"
                ]
              },
              "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[]`), `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\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,
                    "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/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.",
        "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.",
        "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"
              ]
            },
            "required": false,
            "name": "source",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "buy",
                "sell",
                "financing",
                "trade_in"
              ]
            },
            "required": false,
            "name": "intent",
            "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": [
                "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": "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"
          }
        ],
        "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,
                      "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,
                      "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"
                    ]
                  },
                  "intent": {
                    "type": "string",
                    "enum": [
                      "buy",
                      "sell",
                      "financing",
                      "trade_in"
                    ]
                  },
                  "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
                  }
                },
                "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,
                    "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,
                    "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.",
        "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",
              "enum": [
                "all",
                "leads",
                "solicitudes"
              ]
            },
            "required": false,
            "name": "card_kind",
            "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`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Leads"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "minLength": 1
            },
            "required": true,
            "name": "pipeline_id",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "all",
                "leads",
                "solicitudes"
              ]
            },
            "required": false,
            "name": "card_kind",
            "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,
                            "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,
                            "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. `{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,
                    "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
                  },
                  "temperature": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "enum": [
                      "hot",
                      "warm",
                      "cold"
                    ]
                  },
                  "temperature_reason": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 500
                  }
                }
              },
              "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,
                    "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}/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.",
        "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"
                  }
                },
                "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,
                    "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,
                    "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,
                    "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}/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",
                    "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",
                    "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",
                    "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,
                    "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}/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"
      }
    },
    "/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). **`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) and `exclude_id`.\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"
          }
        ],
        "responses": {
          "200": {
            "description": "One page of contacts",
            "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",
                      "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",
                      "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,
                      "display_name": "María González",
                      "named": true,
                      "channels": [
                        "whatsapp"
                      ],
                      "lead_sources": [
                        "website"
                      ]
                    }
                  ],
                  "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",
                          "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",
                          "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,
                          "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",
                          "city": "Providencia",
                          "company_id": null,
                          "social": {
                            "instagram": "@mariagonzalez"
                          },
                          "email_consent": false,
                          "email_status": "subscribed",
                          "avatar_url": null,
                          "social_stats": {},
                          "origin_channel": "manual",
                          "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,
                          "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
                  },
                  "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
                  },
                  "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"
                    ]
                  },
                  "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}$"
                  }
                },
                "required": [
                  "name"
                ]
              },
              "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"
              }
            }
          }
        },
        "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",
                    "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",
                    "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,
                    "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.",
        "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",
                    "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",
                    "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,
                    "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.\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
                  },
                  "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
                  },
                  "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"
                    ]
                  },
                  "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",
                    "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",
                    "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,
                    "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",
                      "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",
                      "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,
                      "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",
                      "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",
                      "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,
                      "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",
                    "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",
                    "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,
                    "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",
                    "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",
                    "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,
                    "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",
                    "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",
                    "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,
                    "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",
                    "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",
                    "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,
                    "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/import": {
      "post": {
        "summary": "Import contacts (dry_run returns a preview 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 5.000 per call). Sending both, or neither, is a 400.\n\n**This is the deduplicating way in.** Each row is matched against the workspace on `external_id`, then `email`, then `phone`. A match is UPDATED — blank fields filled, never clobbered — and anything else is created. Two rows of the same file with the same key: the second is skipped, not inserted again.\n\n`dry_run: true` runs the whole pipeline, including the real dedup lookup, and writes nothing. The report it returns is the same shape as the committed one, so a client can show «X new, Y updates, Z skipped, N errors» and let a person confirm.\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\nA bad row is reported in `rows[]` with its line number and reason; it does not fail the batch. The importer deliberately does not fire per-contact automations, but it DOES emit one `contact.created` per row it created and one `contact.updated` per row it matched.",
        "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
                        }
                      }
                    },
                    "maxItems": 5000
                  },
                  "mapping": {
                    "type": "object",
                    "additionalProperties": {
                      "type": "string"
                    }
                  },
                  "dry_run": {
                    "type": "boolean"
                  },
                  "email_consent_attested": {
                    "type": "boolean"
                  }
                }
              },
              "example": {
                "csv": "name,email,phone\nPedro Ramírez,pedro.ramirez@example.cl,+56987654321\n",
                "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": {
                    "total": 1,
                    "created": 1,
                    "updated": 0,
                    "skipped": 0,
                    "failed": 0,
                    "dry_run": true,
                    "rows": [
                      {
                        "row": 1,
                        "action": "create",
                        "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|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 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\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",
                      "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.",
        "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"
              }
            }
          }
        },
        "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 nine families that already exist — `conversation`, `lead`, `lead_activity`, `appointment`, `note` (contact notes), `recontacto` (outbound attempts), `hold` (safety holds), `ticket` and `audit` (operator actions on this contact) — 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 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/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-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"
                    ]
                  },
                  "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
                  }
                },
                "required": [
                  "provider"
                ]
              },
              "example": {
                "provider": "fintoc",
                "returnUrl": "https://app.vitrinadev.com/settings/complementos",
                "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\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": "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,
                      "tax_clp": 1900000,
                      "tax_treatment": "afecto",
                      "status": "issued",
                      "issued_at": "2026-09-19T13:00:00.000Z",
                      "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**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`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. Supplying it makes the gate BINDING on this request: `price + Σ statutory add-ons` is weighed against `Σ these payments + retoma + credit` BEFORE anything is written, a short plan is a **400 that states the gap** with **no folio consumed**, and a plan that holds is 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-funded sale. **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.\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"
                    ]
                  },
                  "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",
                  "seller_of_record",
                  "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,
                    "tax_clp": 1900000,
                    "tax_treatment": "afecto",
                    "status": "issued",
                    "issued_at": "2026-09-19T13:00:00.000Z",
                    "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**`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,
                    "tax_clp": 1900000,
                    "tax_treatment": "afecto",
                    "status": "issued",
                    "issued_at": "2026-09-19T13:00:00.000Z",
                    "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",
                    "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
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "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}/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 sale cannot authorise its own discount). Raise one with `POST /price-approvals` and have somebody else decide it. 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 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,
                    "tax_clp": 1900000,
                    "tax_treatment": "afecto",
                    "status": "approved",
                    "issued_at": "2026-09-19T13:00:00.000Z",
                    "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/{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,
                    "tax_clp": 1900000,
                    "tax_treatment": "afecto",
                    "status": "voided",
                    "issued_at": "2026-09-19T13:00:00.000Z",
                    "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"
      }
    },
    "/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"
                  },
                  "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\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"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "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"
                  },
                  "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}/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
      }
    },
    "/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"
      }
    },
    "/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,
                      "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"
                  }
                },
                "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,
                    "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,
                    "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
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "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,
                    "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,
                    "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"
                      }
                    ]
                  }
                },
                "required": [
                  "seller_of_record",
                  "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,
                      "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,
                      "tax_clp": 1900000,
                      "tax_treatment": "afecto",
                      "status": "issued",
                      "issued_at": "2026-09-19T13:00:00.000Z",
                      "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`. 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/{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 registry documents for a vehicle",
        "description": "`vehicle_id` is required; there is no tenant-wide list.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Vehicle Attachments"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "vehicle_id",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "padron",
                "cedula",
                "contrato",
                "certificado_anotaciones",
                "factura",
                "otro"
              ]
            },
            "required": false,
            "name": "kind",
            "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"
                    },
                    {
                      "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"
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "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 registry document for a vehicle (multipart/form-data)",
        "description": "Capped at the configured max size; the file part is required.",
        "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",
                      "otro"
                    ]
                  },
                  "subject_contact_id": {
                    "type": "string",
                    "format": "uuid"
                  },
                  "file": {}
                },
                "required": [
                  "vehicle_id",
                  "kind"
                ],
                "additionalProperties": false
              },
              "example": {
                "vehicle_id": "e1e1e1e1-0000-4000-8000-000000000001",
                "kind": "padron",
                "file": "(binary file part)"
              }
            }
          }
        },
        "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"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "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": "Remove a vehicle attachment",
        "description": "Erases the storage object first, then the row.",
        "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
      }
    },
    "/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; and, unless `skip_eval` is set, start the graph eval suite. 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. The eval run is fire-and-forget: it is NOT awaited and its failure is logged, never surfaced, so a 200 here says the graph published, not that its tests passed. Poll GET /ai-agent-graphs/{id}/tests/runs for that. 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",
            "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": [],
                      "team_id": "cccccccc-0000-4000-8000-000000000001",
                      "assignee_user_ids": null,
                      "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": 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 assignment rule",
        "description": "Exactly one destination: `team_id` OR a non-empty `assignee_user_ids`. Empty/absent `match_sources` / `match_channels` are wildcards. 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).",
        "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
                  },
                  "team_id": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "uuid"
                  },
                  "assignee_user_ids": {
                    "type": [
                      "array",
                      "null"
                    ],
                    "items": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "maxItems": 200
                  },
                  "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": [],
                    "team_id": "cccccccc-0000-4000-8000-000000000001",
                    "assignee_user_ids": null,
                    "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
                  }
                }
              },
              "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": [],
                    "team_id": "cccccccc-0000-4000-8000-000000000001",
                    "assignee_user_ids": null,
                    "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 — the destination stays exactly one thing. `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
                  },
                  "team_id": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "uuid"
                  },
                  "assignee_user_ids": {
                    "type": [
                      "array",
                      "null"
                    ],
                    "items": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "maxItems": 200
                  },
                  "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": [],
                    "team_id": "cccccccc-0000-4000-8000-000000000001",
                    "assignee_user_ids": null,
                    "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": [],
                      "team_id": "cccccccc-0000-4000-8000-000000000001",
                      "assignee_user_ids": null,
                      "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/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/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/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_-]+$"
            }
          }
        ]
      }
    },
    "/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"
      }
    },
    "/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/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.\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.\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
                  },
                  "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
                  },
                  "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://vitrinadev-api.atribu.app/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. 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. `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"
                        ]
                      },
                      "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.\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-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"
      }
    },
    "/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,
                    "model": "xiaomi/mimo-v2.6-flash",
                    "temperature": null,
                    "default_max_steps": 5,
                    "reasoning_effort": "high",
                    "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_model": 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:write`.",
        "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: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"
          }
        ],
        "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 MODEL GATEWAY REFUSED (exhausted credits, a rate limit, a dead provider) is NOT an empty answer: the attempt is recorded with `outcome: \"error\"` carrying the provider's own sentence, and the call answers **502**. 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\".",
        "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"
      }
    },
    "/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.",
        "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"
      }
    },
    "/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 model gateway REFUSED (exhausted credits, a rate limit, a dead provider) is a **502** carrying the provider’s own sentence 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`.",
        "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
                      }
                    ],
                    "investigation": {
                      "evidence": "red_run",
                      "model": "xiaomi/mimo-v2.6-flash"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "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/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.",
        "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"
      }
    },
    "/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"
      }
    },
    "/ai-agents/{id}/publish-gate": {
      "get": {
        "summary": "Would publishing this agent be refused by its evals?",
        "description": "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` make `POST /ai-agents/{id}/publish` answer **409** `{ error, code: 'evals_blocked' | 'evals_stale', gate }` unless the body carries `force: true`.",
        "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"
      }
    },
    "/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"
      }
    },
    "/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.\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": "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/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).",
        "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,
                    "sales_team_id": "cccccccc-0000-4000-8000-000000000001",
                    "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, `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
                  },
                  "sales_team_id": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "uuid"
                  }
                }
              },
              "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,
                    "sales_team_id": "cccccccc-0000-4000-8000-000000000001",
                    "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": "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": "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,
                      "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,
                      "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,
                      "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,
                      "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) or `blocked` (an admin block covers it). All four 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"
                  },
                  "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,
                    "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,
                    "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`.",
        "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,
                    "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,
                    "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) or `blocked` (an admin block covers it). All four 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\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
                  }
                }
              },
              "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,
                    "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,
                    "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
      }
    },
    "/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": [],
                      "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": [],
                      "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"
                    }
                  },
                  "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": [],
                  "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": [],
                  "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"
                    }
                  },
                  "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": [],
                  "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"
      }
    },
    "/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`.\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"
          }
        ],
        "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",
                      "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,
                      "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/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.\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"
            },
            "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"
            },
            "required": false,
            "name": "from",
            "in": "query"
          },
          {
            "schema": {
              "type": "string"
            },
            "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"
          }
        ],
        "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/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. 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
                  }
                },
                "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,
                    "booking_landing_id": 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,
                    "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"
                  }
                },
                "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,
                    "booking_landing_id": 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,
                    "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/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"
                  },
                  "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/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}/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. 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"
                  }
                },
                "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/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\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. 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/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
      }
    },
    "/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 Atribu 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 Atribu. 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 Atribu, 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, `closed_won`→Purchase (event value), and `payment_received` with its Meta destination DISABLED (cash ledger only). Idempotent: matched to existing rules by definition. 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 dealer's ad sets optimize for the events Vitrina sends",
        "description": "Atribu's per-rule verdicts and remediation, verbatim (served from a 6h cache; `force=1` re-verifies live — still a read). `privacy` reports whether a clinic profile carries `platform_safe`. Requires `ads:read`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Ads"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "enum": [
                "0",
                "1",
                "true",
                "false"
              ],
              "description": "Re-verify live against Meta instead of Atribu's 6h cache. Still a read — several Meta API calls, use sparingly."
            },
            "required": false,
            "description": "Re-verify live against Meta instead of Atribu's 6h cache. Still a read — several Meta API calls, use sparingly.",
            "name": "force",
            "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",
                        "status": "wired",
                        "remediation": null
                      }
                    ],
                    "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",
                        "status": "wired",
                        "remediation": null
                      }
                    ],
                    "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_-]+$"
            }
          }
        ]
      }
    },
    "/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\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"
                    ]
                  },
                  "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
                  },
                  "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/{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",
                "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",
                      "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
      }
    },
    "/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"
      }
    },
    "/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",
                        "audit:read",
                        "analytics:read",
                        "analytics:write",
                        "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",
                        "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",
                        "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",
                        "price_approval:read",
                        "price_approval:request",
                        "price_approval: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
                  },
                  "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"
                    ]
                  }
                },
                "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.",
        "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": {
                    "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
                    },
                    "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}": {
      "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
                  },
                  "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"
                    ]
                  }
                }
              },
              "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, plus the payment record. Requires `amount_venta_clp`; `paid_at` records when the owner was actually paid.\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"
                  }
                },
                "required": [
                  "amount_venta_clp"
                ]
              },
              "example": {
                "amount_venta_clp": 8900000,
                "paid_at": "2026-09-18T15:00:00.000Z"
              }
            }
          }
        },
        "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,
                      "retiro_motivo": null,
                      "retiro_by": null,
                      "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.",
        "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,
                    "retiro_motivo": null,
                    "retiro_by": null,
                    "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\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.",
        "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": "string",
              "enum": [
                "created_at",
                "updated_at",
                "last_message_date"
              ],
              "default": "created_at"
            },
            "required": false,
            "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"
          }
        ],
        "responses": {
          "200": {
            "description": "Conversations",
            "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",
                      "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,
                      "tags": [
                        {
                          "id": "12121212-0000-4000-8000-000000000001",
                          "name": "garantia",
                          "display_name": "Garantía"
                        }
                      ],
                      "last_call": 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.",
        "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)",
        "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`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
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "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 click-to-WhatsApp 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.",
        "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": {},
                    "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",
                    "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,
                    "tags": [
                      {
                        "id": "12121212-0000-4000-8000-000000000001",
                        "name": "garantia",
                        "display_name": "Garantía"
                      }
                    ],
                    "last_call": null,
                    "ad_origin_nudge": null,
                    "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.",
        "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,
                      "tokens": 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"
                    },
                    {
                      "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,
                      "tokens": 0,
                      "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"
                    },
                    {
                      "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,
                      "tokens": 0,
                      "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"
                    }
                  ],
                  "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\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,
                    "tokens": 0,
                    "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, 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"
                  }
                },
                "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,
                      "tokens": 0,
                      "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/{id}/scheduled-messages": {
      "get": {
        "summary": "List pending scheduled sends",
        "description": "The replies waiting to fire on this conversation — PENDING ones only: a scheduled message that has gone out, been cancelled or been stopped by the política de envíos at fire time 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",
                      "error": null,
                      "message_id": null,
                      "policy_acknowledged": [
                        "conversation_human_owned"
                      ],
                      "created_at": "2026-09-22T11:09:04.112Z",
                      "updated_at": "2026-09-22T11:09:04.112Z"
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "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. A message that already fired (or was already cancelled) 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": 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`)."
                  }
                },
                "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,
                    "tokens": 0,
                    "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, 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. 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
                  },
                  "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,
                    "tokens": 0,
                    "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, 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"
                      }
                    },
                    "tokens": 0,
                    "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, 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\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"
              }
            }
          }
        },
        "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,
                    "tokens": 0,
                    "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, 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,
                    "tokens": 0,
                    "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, 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}/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"
      }
    },
    "/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 and capped at 200 keys.\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"
          }
        ],
        "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",
                      "count": 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"
      }
    },
    "/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"
      }
    },
    "/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.",
        "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)"
              }
            }
          }
        },
        "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.",
        "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"
      }
    },
    "/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.",
        "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"
      }
    },
    "/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.",
        "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)"
              }
            }
          }
        },
        "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/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.",
        "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
                  },
                  "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,
                      "cost_usd": 0.0508,
                      "tokens": 322522,
                      "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,
                    "cost_usd": 0.0508,
                    "cost_per_conversation": 0.0042,
                    "tokens": 322522,
                    "avg_gen_latency_ms": 7938,
                    "tool_calls": 21,
                    "resolutions_series": [
                      {
                        "bucket": "2026-09-22",
                        "value": 3
                      }
                    ],
                    "handoff_series": [
                      {
                        "bucket": "2026-09-22",
                        "value": 1
                      }
                    ],
                    "cost_series": [
                      {
                        "bucket": "2026-09-22",
                        "value": 0.0064
                      }
                    ]
                  },
                  "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\nAdmin-scoped (`integrations:read`), like the rest of Integraciones — that scope lives only in the admin bundle, so a `supervisor` or `agent` gets 403 here and never sees the card. The two writes need `integrations:write` on top.",
        "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 Atribu’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 Atribu as `return_url`. An origin that is not the app’s is a 400 here rather than a silent substitution — Atribu 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.** Atribu’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 the global `ATRIBU_API_KEY`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Integrations"
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/MetaAdsConnectBody"
              },
              "example": {
                "return_to": "https://app.vitrinadev.com/settings/integraciones?meta_ads=return"
              }
            }
          }
        },
        "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.atribu.app/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 Atribu 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** — Atribu 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 Atribu, 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).",
        "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",
                    "account": {
                      "external_account_id": "act_1234567890",
                      "name": "Automotora Ejemplo",
                      "currency": "CLP",
                      "timezone": "America/Santiago",
                      "delivers_spend": true,
                      "atribu_connection_id": "conn_9f3a2d7c8b1e"
                    },
                    "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 Atribu’s `connections/{id}/disconnect` operation 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 Atribu to revoke — `pending` / `pending_selection` with no connection — is cancelled LOCALLY: no Atribu call, the row moves straight to `disconnected`, and the pending hand-off simply expires on its own (Atribu has no endpoint to cancel one early). A row already `disconnected` answers 200 unchanged (idempotent).\n\n409 `sync_in_flight` when Atribu 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 `atribu_refused` when Atribu’s own 403 says the delegated key is no longer good for this connection — also left unchanged. A 404 from Atribu (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",
                    "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 Atribu’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 (Atribu’s `connections?channel=meta_ads` operation) 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 — Atribu’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 Atribu’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 Atribu is ever called.",
        "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",
                    "account": {
                      "external_account_id": "act_1234567890",
                      "name": "Automotora Ejemplo",
                      "currency": "CLP",
                      "timezone": "America/Santiago",
                      "delivers_spend": true,
                      "atribu_connection_id": "conn_9f3a2d7c8b1e"
                    },
                    "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/ads": {
      "get": {
        "summary": "The tenant's click-to-WhatsApp ads, with connect/greeting checks",
        "description": "Proxies Atribu’s 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\nAn admin-scoped read (`integrations:read` is admin-only in the scope catalog, 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 Atribu partner token, so there is no scope to list ads under.\n\n`cursor` is Atribu’s own opaque keyset cursor (its `pagination.cursor`, a Meta ad id ascending), passed straight through as `after`. `next_cursor` is `null` once Atribu 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 an Atribu 5xx, timeout, or a response shape this route does not recognise. 502 `partner_token_invalid` when Atribu 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"
      }
    },
    "/ads/overview": {
      "get": {
        "summary": "Ad spend, attributed revenue, ROAS and outcomes for a window",
        "description": "Proxies Atribu’s `overview` operation: `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 Atribu’s exact-decimal wire string (ADR 0018) to a number ONCE, at the provider mapper.",
        "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": "Atribu attribution model. Defaults to `last_touch`."
            },
            "required": false,
            "description": "Atribu 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
                      }
                    ]
                  }
                }
              }
            }
          },
          "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": "Proxies Atribu’s `campaigns` operation (the ranked top-N shape — no `level` is ever sent, so this is always the campaign rollup, not 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 Atribu 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.",
        "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": "Atribu attribution model. Defaults to `last_touch`."
            },
            "required": false,
            "description": "Atribu 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,
                        "top_ad": {
                          "external_id": "120212000000000009",
                          "name": "Video testimonio 15s",
                          "creative_thumbnail_url": "https://cdn.vitrinadev.com/examples/ads/video-testimonio-15s.jpg"
                        },
                        "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
                          }
                        ]
                      }
                    ]
                  }
                }
              }
            }
          },
          "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": "Proxies Atribu’s `campaigns/{id}/conversions` operation: 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": "Atribu attribution model. Defaults to `last_touch`."
            },
            "required": false,
            "description": "Atribu 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 Atribu’s internal `campaign_id`."
            },
            "required": true,
            "description": "The campaign’s PLATFORM id — the `campaign_external_id` an `/ads/campaigns` row returns, never Atribu’s internal `campaign_id`.",
            "name": "campaign_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/creatives": {
      "get": {
        "summary": "Top-performing ads (cohort-normalized creative scoring)",
        "description": "Proxies Atribu’s `top-performers` operation — 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, a probability, never ROAS) 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 Atribu profile, so it cannot call Atribu’s cross-profile `/workspaces/{id}/top-performers` (that route is session-bearer-only and answers `403 insufficient_scope` to any API key).",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Ads"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "enum": [
                "7d",
                "14d",
                "28d",
                "lifetime"
              ],
              "description": "A rolling lookback, not a date range — Atribu’s `top-performers` operation takes no `from`/`to`. Defaults to `28d`."
            },
            "required": false,
            "description": "A rolling lookback, not a date range — Atribu’s `top-performers` operation takes no `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",
              "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,
                        "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/health": {
      "get": {
        "summary": "Attribution trust, UTM quality and attribution coverage",
        "description": "One combined read over three Atribu operations, so a dashboard panel needs one call, not three:\n\n* `trust` — Atribu’s `trust` operation: 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` — Atribu’s `quality/utm` operation: 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` — Atribu’s `quality/attribution` operation: the conversion side — how many of the window’s conversions carry a real ad id, an `fbclid` only, or nothing, partitioning `total_conversions`.\n\n⚠ Named `/attribution-coverage` in this ticket’s original prose — the live Atribu spec names this operation `quality/attribution`; there is no separate `/attribution-coverage` path. Same read, corrected name.",
        "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": "Atribu attribution model. Defaults to `last_touch`."
            },
            "required": false,
            "description": "Atribu 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": []
                      },
                      "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
                    }
                  }
                }
              }
            }
          },
          "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) or `template` (deterministic — first week, Meta disconnected, no spend reading, the model slow or rejected). Both read identically. A briefing is cached per facts hash for 24 h, so two tabs and a reload read the same text; when the model misses its budget the template answers and the model briefing is finished in the background for the next load.\n\n`lead.figure` names the number the lead is built around (`display` is its exact substring in `lead.text`). `lines[].ask` is a suggested follow-up question. `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",
              "description": "Attribution model. Defaults to `last_touch`."
            },
            "required": false,
            "description": "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 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 36,4% de tus tratamientos vino de anuncios.",
                        "fact_keys": [
                          "best_campaign_name",
                          "best_campaign_roas",
                          "ads_share"
                        ],
                        "ask": "¿Qué campaña me conviene subir?"
                      }
                    ],
                    "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/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 a worn-out ad, raise a winning ad set’s budget, re-check the conversion wiring, re-scan the ad links — or, when Meta is not connected, only a `nav` card to reconnect it. Nothing here runs by itself: preview → confirm → execute.\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).\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"
              ],
              "description": "The screen whose action rail to list (`atribuidos` has none)."
            },
            "required": true,
            "description": "The screen whose action rail to list (`atribuidos` has none).",
            "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": [
                "adset_duplicate_swap"
              ],
              "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."
            },
            "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.",
            "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"
                      ],
                      "primary": true,
                      "title": "Pausar «Video testimonio · 30 s»",
                      "why": "Está desgastado: la gente ya lo vio demasiadas veces y **$184.300** de gasto rinde cada vez menos.",
                      "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
                      },
                      "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
                      },
                      "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
                    }
                  ]
                }
              }
            }
          },
          "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 — the consent lines the owner must accept. 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).",
        "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}$"
                  },
                  "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"
                }
              }
            }
          }
        },
        "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**Duplicate-and-swap** needs 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`).",
        "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": "Duplicate-and-swap ONLY, and required there: the owner's acceptance of `consent_terms` from the preview — 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"
      }
    },
    "/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": [
                "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"
                      },
                      "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"
                      },
                      "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`). `error.message` is Spanish copy ready for the card.",
        "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",
                    "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`). **`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"
      }
    },
    "/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.atribu.app/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.\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, `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`, seen minutes ago, `source: tracker`) without the entitlement and without reading the real tag — like every other Ads read. Admin-scoped (`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 tag detection status",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/AdsTrackingStatus"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "installed": 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/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",
                "appointment_booked",
                "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"
                        },
                        "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."
                        }
                      },
                      "required": [
                        "next_before",
                        "server_time",
                        "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
                    },
                    {
                      "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
                    }
                  ],
                  "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 path to its outcome",
        "description": "The touches that led to one recorded outcome, oldest first, ending at the outcome: ad views and clicks (ad and campaign names), site visits, conversations. From the attribution engine (`source: engine`) when it credited the outcome and the delegated key may read people; otherwise Vitrina’s own record of this contact’s outcomes (`source: vitrina`) — still a path, without the ad touches. Never carries a URL, referrer, email or device. 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",
              "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": [
                      {
                        "kind": "ad",
                        "label": "Reel · Antes y después",
                        "detail": "Ortodoncia invisible · Septiembre",
                        "at": "2026-09-19T21:10:00.000Z",
                        "channel": "meta"
                      },
                      {
                        "kind": "click",
                        "label": "Reel · Antes y después",
                        "detail": "Ortodoncia invisible · Septiembre",
                        "at": "2026-09-20T13:02:00.000Z",
                        "channel": "meta"
                      },
                      {
                        "kind": "chat",
                        "label": "Conversación",
                        "detail": null,
                        "at": "2026-09-20T13:03:00.000Z",
                        "channel": "whatsapp"
                      },
                      {
                        "kind": "outcome",
                        "label": "Presupuesto aceptado",
                        "detail": null,
                        "at": "2026-09-22T19:40:12.000Z"
                      }
                    ],
                    "touch_count": 3,
                    "credited_weight": 1,
                    "model": "last_touch",
                    "source": "engine"
                  }
                }
              }
            }
          },
          "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` — not a JSON body. The filename and content type come from the part itself. Capped at 25 MB; an empty file and a missing part are both 400s.\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. To upload straight onto an agent, 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)"
              }
            }
          }
        },
        "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.",
        "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"
                  }
                }
              },
              "example": {
                "file": "(binary file part)"
              }
            }
          }
        },
        "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": "a1",
                          "type": "add_tag",
                          "value": [
                            "resuelto"
                          ]
                        },
                        {
                          "id": "a2",
                          "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": "a1",
                    "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": "a1",
                        "type": "add_tag",
                        "value": [
                          "resuelto"
                        ]
                      },
                      {
                        "id": "a2",
                        "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": "a1",
                        "type": "add_tag",
                        "value": [
                          "resuelto"
                        ]
                      },
                      {
                        "id": "a2",
                        "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": "a1",
                        "type": "add_tag",
                        "value": [
                          "resuelto"
                        ]
                      },
                      {
                        "id": "a2",
                        "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}/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": "a1",
                          "type": "add_tag",
                          "value": [
                            "resuelto"
                          ]
                        },
                        {
                          "id": "a2",
                          "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"
      }
    },
    "/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 buyers each with a conversation and a lead, 10 cotizaciones and 5 reservas — and 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, no plates or VINs). 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
                  },
                  "vehicle_count": {
                    "type": "integer",
                    "minimum": 10,
                    "maximum": 200
                  },
                  "contact_count": {
                    "type": "integer",
                    "minimum": 15,
                    "maximum": 300
                  }
                },
                "additionalProperties": false
              },
              "example": {
                "seed": "docs-2697-demo"
              }
            }
          }
        },
        "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 Alameda (demostración)",
                      "slug": "autos-alameda-demo-3"
                    },
                    "vertical": "automotive",
                    "seed": "docs-2697-demo",
                    "stats": {
                      "vehicles": 40,
                      "contacts": 30,
                      "leads": 30,
                      "quotes": 10,
                      "reservations": 5
                    },
                    "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`) and the rows removed (`deleted`)",
            "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": 30,
                      "lead": 30
                    },
                    "deleted": {
                      "vehicle": 40,
                      "contact": 30,
                      "sandbox_outbound_capture": 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,
        "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.",
        "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. Both default to `all`.\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` 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"
                    ]
                  }
                },
                "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. Both default to `all`.\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` 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"
                    ]
                  }
                }
              },
              "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
      }
    },
    "/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.",
        "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"
      }
    },
    "/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`. 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,
                    "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\". 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",
                        "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}/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\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",
                      "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⚠ 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
                    }
                  ],
                  "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"
      }
    },
    "/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": {}
                  },
                  "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": {}
                  },
                  "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"
                    ]
                  },
                  "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"
                              ]
                            },
                            "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"
                    ]
                  },
                  "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"
                              ]
                            },
                            "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": {
      "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"
          }
        ],
        "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",
                      "body_text": "Hola {{1}}, el {{2}} que consultaste sigue disponible. ¿Seguimos viéndolo?",
                      "header_text": 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`.",
        "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
                  },
                  "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"
                          ]
                        }
                      ]
                    },
                    "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"
                ]
              },
              "example": {
                "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"
              }
            }
          }
        },
        "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",
                    "body_text": "Hola {{1}}, el {{2}} que consultaste sigue disponible. ¿Seguimos viéndolo?",
                    "header_text": 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/{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",
                    "body_text": "Hola {{1}}, el {{2}} que consultaste sigue disponible. ¿Seguimos viéndolo?",
                    "header_text": 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",
                    "body_text": "Hola {{1}}, el {{2}} que consultaste sigue disponible. ¿Seguimos viéndolo?",
                    "header_text": 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",
                    "body_text": "Hola {{1}}, el {{2}} que consultaste sigue disponible. ¿Seguimos viéndolo?",
                    "header_text": 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.",
        "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",
                    "body_text": "Hola {{1}}, el {{2}} que consultaste sigue disponible. ¿Seguimos viéndolo?",
                    "header_text": 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/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",
                    "body_text": "Hola {{1}}, el {{2}} que consultaste sigue disponible. ¿Seguimos viéndolo?",
                    "header_text": 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
      }
    },
    "/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, which the `/managed/*` builder never sees. `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": "e3e3e3e3-0000-4000-8000-000000000001",
                      "name": "Agenda tu visita",
                      "status": "PUBLISHED",
                      "categories": [
                        "APPOINTMENT_BOOKING"
                      ]
                    }
                  ],
                  "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.",
        "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": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "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",
                      "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
                              }
                            ]
                          }
                        ]
                      },
                      "validation_errors": null,
                      "preview_url": "https://business.facebook.com/wa/manage/flows/1200300400500600/preview/",
                      "preview_expires_at": "2026-09-15T19: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": "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`.",
        "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
                            },
                            "components": {
                              "type": "array",
                              "items": {
                                "oneOf": [
                                  {
                                    "type": "object",
                                    "properties": {
                                      "kind": {
                                        "type": "string",
                                        "enum": [
                                          "heading"
                                        ]
                                      },
                                      "text": {
                                        "type": "string",
                                        "minLength": 1,
                                        "maxLength": 80
                                      }
                                    },
                                    "required": [
                                      "kind",
                                      "text"
                                    ]
                                  },
                                  {
                                    "type": "object",
                                    "properties": {
                                      "kind": {
                                        "type": "string",
                                        "enum": [
                                          "subheading"
                                        ]
                                      },
                                      "text": {
                                        "type": "string",
                                        "minLength": 1,
                                        "maxLength": 80
                                      }
                                    },
                                    "required": [
                                      "kind",
                                      "text"
                                    ]
                                  },
                                  {
                                    "type": "object",
                                    "properties": {
                                      "kind": {
                                        "type": "string",
                                        "enum": [
                                          "body"
                                        ]
                                      },
                                      "text": {
                                        "type": "string",
                                        "minLength": 1,
                                        "maxLength": 4096
                                      }
                                    },
                                    "required": [
                                      "kind",
                                      "text"
                                    ]
                                  },
                                  {
                                    "type": "object",
                                    "properties": {
                                      "kind": {
                                        "type": "string",
                                        "enum": [
                                          "caption"
                                        ]
                                      },
                                      "text": {
                                        "type": "string",
                                        "minLength": 1,
                                        "maxLength": 409
                                      }
                                    },
                                    "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
                                      }
                                    },
                                    "required": [
                                      "kind",
                                      "src"
                                    ]
                                  },
                                  {
                                    "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"
                                        ]
                                      },
                                      "helper_text": {
                                        "type": "string",
                                        "maxLength": 80
                                      }
                                    },
                                    "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
                                      }
                                    },
                                    "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
                                            }
                                          },
                                          "required": [
                                            "id",
                                            "title"
                                          ]
                                        },
                                        "minItems": 1,
                                        "maxItems": 200
                                      }
                                    },
                                    "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
                                            }
                                          },
                                          "required": [
                                            "id",
                                            "title"
                                          ]
                                        },
                                        "minItems": 1,
                                        "maxItems": 20
                                      }
                                    },
                                    "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
                                            }
                                          },
                                          "required": [
                                            "id",
                                            "title"
                                          ]
                                        },
                                        "minItems": 1,
                                        "maxItems": 20
                                      },
                                      "min_selected": {
                                        "type": "integer",
                                        "minimum": 1
                                      },
                                      "max_selected": {
                                        "type": "integer",
                                        "minimum": 1
                                      }
                                    },
                                    "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
                                      }
                                    },
                                    "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"
                                      }
                                    },
                                    "required": [
                                      "kind",
                                      "name",
                                      "label"
                                    ]
                                  }
                                ]
                              },
                              "maxItems": 49
                            }
                          },
                          "required": [
                            "title",
                            "components"
                          ]
                        },
                        "minItems": 1,
                        "maxItems": 10
                      }
                    },
                    "required": [
                      "screens"
                    ]
                  }
                },
                "required": [
                  "messaging_account_id",
                  "name",
                  "categories",
                  "builder_state"
                ]
              },
              "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
                        }
                      ]
                    }
                  ]
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The created flow",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "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",
                    "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
                            }
                          ]
                        }
                      ]
                    },
                    "validation_errors": null,
                    "preview_url": null,
                    "preview_expires_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-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",
        "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.",
        "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`",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "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",
                    "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
                            }
                          ]
                        }
                      ]
                    },
                    "validation_errors": null,
                    "preview_url": "https://business.facebook.com/wa/manage/flows/1200300400500600/preview/",
                    "preview_expires_at": "2026-09-15T19: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 draft flow",
        "description": "A published flow is immutable on Meta’s side — this is a **409** once `status` is `PUBLISHED`. `POST /whatsapp-flows/managed/{id}/duplicate` is the way to keep iterating: it always creates a fresh `DRAFT` copy.",
        "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 draft flow",
        "description": "A published flow is immutable on Meta’s side — this is a **409** once `status` is `PUBLISHED`. `POST /whatsapp-flows/managed/{id}/duplicate` is the way to keep iterating: it always creates a fresh `DRAFT` copy.",
        "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
                            },
                            "components": {
                              "type": "array",
                              "items": {
                                "oneOf": [
                                  {
                                    "type": "object",
                                    "properties": {
                                      "kind": {
                                        "type": "string",
                                        "enum": [
                                          "heading"
                                        ]
                                      },
                                      "text": {
                                        "type": "string",
                                        "minLength": 1,
                                        "maxLength": 80
                                      }
                                    },
                                    "required": [
                                      "kind",
                                      "text"
                                    ]
                                  },
                                  {
                                    "type": "object",
                                    "properties": {
                                      "kind": {
                                        "type": "string",
                                        "enum": [
                                          "subheading"
                                        ]
                                      },
                                      "text": {
                                        "type": "string",
                                        "minLength": 1,
                                        "maxLength": 80
                                      }
                                    },
                                    "required": [
                                      "kind",
                                      "text"
                                    ]
                                  },
                                  {
                                    "type": "object",
                                    "properties": {
                                      "kind": {
                                        "type": "string",
                                        "enum": [
                                          "body"
                                        ]
                                      },
                                      "text": {
                                        "type": "string",
                                        "minLength": 1,
                                        "maxLength": 4096
                                      }
                                    },
                                    "required": [
                                      "kind",
                                      "text"
                                    ]
                                  },
                                  {
                                    "type": "object",
                                    "properties": {
                                      "kind": {
                                        "type": "string",
                                        "enum": [
                                          "caption"
                                        ]
                                      },
                                      "text": {
                                        "type": "string",
                                        "minLength": 1,
                                        "maxLength": 409
                                      }
                                    },
                                    "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
                                      }
                                    },
                                    "required": [
                                      "kind",
                                      "src"
                                    ]
                                  },
                                  {
                                    "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"
                                        ]
                                      },
                                      "helper_text": {
                                        "type": "string",
                                        "maxLength": 80
                                      }
                                    },
                                    "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
                                      }
                                    },
                                    "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
                                            }
                                          },
                                          "required": [
                                            "id",
                                            "title"
                                          ]
                                        },
                                        "minItems": 1,
                                        "maxItems": 200
                                      }
                                    },
                                    "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
                                            }
                                          },
                                          "required": [
                                            "id",
                                            "title"
                                          ]
                                        },
                                        "minItems": 1,
                                        "maxItems": 20
                                      }
                                    },
                                    "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
                                            }
                                          },
                                          "required": [
                                            "id",
                                            "title"
                                          ]
                                        },
                                        "minItems": 1,
                                        "maxItems": 20
                                      },
                                      "min_selected": {
                                        "type": "integer",
                                        "minimum": 1
                                      },
                                      "max_selected": {
                                        "type": "integer",
                                        "minimum": 1
                                      }
                                    },
                                    "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
                                      }
                                    },
                                    "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"
                                      }
                                    },
                                    "required": [
                                      "kind",
                                      "name",
                                      "label"
                                    ]
                                  }
                                ]
                              },
                              "maxItems": 49
                            }
                          },
                          "required": [
                            "title",
                            "components"
                          ]
                        },
                        "minItems": 1,
                        "maxItems": 10
                      }
                    },
                    "required": [
                      "screens"
                    ]
                  }
                }
              },
              "example": {
                "name": "Agenda tu visita al showroom"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The updated flow",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "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",
                    "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
                            }
                          ]
                        }
                      ]
                    },
                    "validation_errors": null,
                    "preview_url": "https://business.facebook.com/wa/manage/flows/1200300400500600/preview/",
                    "preview_expires_at": "2026-09-15T19: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": "The flow is published and immutable"
          },
          "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. Publishing is one-way — see `duplicate` for the way to keep editing afterwards.",
        "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": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "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",
                    "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
                            }
                          ]
                        }
                      ]
                    },
                    "validation_errors": null,
                    "preview_url": "https://business.facebook.com/wa/manage/flows/1200300400500600/preview/",
                    "preview_expires_at": "2026-09-15T19:20:00.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": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "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",
                    "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
                            }
                          ]
                        }
                      ]
                    },
                    "validation_errors": null,
                    "preview_url": "https://business.facebook.com/wa/manage/flows/1200300400500600/preview/",
                    "preview_expires_at": "2026-09-15T19: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-flows/managed/{id}/duplicate": {
      "post": {
        "summary": "Duplicate a flow as a new draft",
        "description": "The escape hatch for an immutable `PUBLISHED` flow: copies its `builder_state` 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": {}
                  }
                },
                "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",
                    "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
                            }
                          ]
                        }
                      ]
                    },
                    "validation_errors": null,
                    "preview_url": "https://business.facebook.com/wa/manage/flows/1200300400500600/preview/",
                    "preview_expires_at": "2026-09-15T19: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-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"
      }
    },
    "/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",
        "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,
        "description": "Get a clinic 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."
      },
      "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,
                      "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": "status_change_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/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
                  }
                },
                "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"
                  }
                }
              },
              "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.\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": "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"
                    ]
                  },
                  "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/{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"
                    ]
                  },
                  "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": {}
}
