{
  "openapi": "3.1.0",
  "info": {
    "title": "API pública do Promec",
    "version": "1.0.0",
    "description": "API REST da instância da oficina. Autenticação por chave (Bearer ou X-Api-Key), respostas JSON, paginação por cursor, datas ISO 8601 em UTC e valores em euros com duas casas decimais.",
    "x-languages": [
      "es",
      "en",
      "ca",
      "pt",
      "fr",
      "bg"
    ],
    "x-guides": [
      {
        "id": "budget-and-appointment",
        "title": "Guia: criar um orçamento e propor um agendamento",
        "intro": "Fluxo típico de um CRM ou de um site de agendamento. Todas as requisições POST levam Idempotency-Key (um UUID novo por operação; repita-o só ao tentar de novo a mesma). Você precisa de uma chave live com clients:write, vehicles:write, budgets:write, appointments:read e appointments:write.",
        "steps": [
          {
            "title": "1. Cliente: criar ou recuperar o existente",
            "operationId": "createClient",
            "method": "POST",
            "path": "/api/v1/clients",
            "body": "{ \"name\": \"Laura Gómez\", \"phone\": \"600111222\", \"email\": \"laura@ejemplo.com\" }",
            "note": "Com ?on_conflict=return_existing, se o telefone, o e-mail ou o NIF já existirem, você recebe esse cliente (200) em vez de 409."
          },
          {
            "title": "2. Veículo do cliente",
            "operationId": "createVehicle",
            "method": "POST",
            "path": "/api/v1/vehicles",
            "body": "{ \"client_id\": 1204, \"plate\": \"1234KLM\", \"brand\": \"Seat\", \"model\": \"León\" }",
            "note": "Se a placa for de outro cliente: 409 vehicle_belongs_to_other_client (a oficina decide)."
          },
          {
            "title": "3. Orçamento com seus itens",
            "operationId": "createBudget",
            "method": "POST",
            "path": "/api/v1/budgets",
            "body": "{ \"client_id\": 1204, \"vehicle_id\": 871, \"lines\": [{ \"description\": \"Troca de óleo e filtro\", \"quantity\": 1, \"unit_price\": 65 }] }",
            "note": "A resposta traz os totais calculados e tracking_url: compartilhe com o cliente para que ele aprove e assine."
          },
          {
            "title": "4. Horários livres",
            "operationId": "getAvailability",
            "method": "GET",
            "path": "/api/v1/appointments/availability",
            "body": "?from=2026-10-14&to=2026-10-18&duration_minutes=60",
            "note": null
          },
          {
            "title": "5. Propor o agendamento",
            "operationId": "createAppointment",
            "method": "POST",
            "path": "/api/v1/appointments",
            "body": "{ \"budget_id\": 1234, \"start\": \"2026-10-14T09:00:00+02:00\", \"duration_minutes\": 60 }",
            "note": "No modo «propor» fica status=proposed até a oficina confirmar (você recebe appointment.confirmed por webhook). Se o horário já foi ocupado: 409 slot_unavailable com alternativas."
          }
        ]
      }
    ]
  },
  "servers": [
    {
      "url": "https://sua-oficina.example",
      "description": "Instância da oficina"
    }
  ],
  "tags": [
    {
      "name": "Geral",
      "x-area": "general"
    },
    {
      "name": "Clientes",
      "x-area": "clients"
    },
    {
      "name": "Veículos",
      "x-area": "vehicles"
    },
    {
      "name": "Orçamentos",
      "x-area": "budgets"
    },
    {
      "name": "Faturas",
      "x-area": "invoices"
    },
    {
      "name": "Marcações",
      "x-area": "appointments"
    },
    {
      "name": "Catálogo",
      "x-area": "catalog"
    },
    {
      "name": "Comunicações",
      "x-area": "communications"
    },
    {
      "name": "Webhooks",
      "x-area": "webhooks"
    }
  ],
  "paths": {
    "/api/v1/ping": {
      "get": {
        "operationId": "ping",
        "tags": [
          "Geral"
        ],
        "summary": "Testar a conexão",
        "description": "Retorna o nome da chave, o ambiente, as permissões e o estado dos limites. Qualquer chave válida serve.\n\nQualquer chave válida, sem permissão específica.",
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Resposta bem-sucedida",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "epoch (s)"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Ping"
                }
              }
            }
          },
          "400": {
            "description": "Erro — bad_request: A requisição não é válida.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Erro — missing_api_key: Falta a chave de API: envie no cabeçalho Authorization: Bearer pt_… ou X-Api-Key. | invalid_api_key: A chave de API não é válida para esta instância.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Erro — ip_not_allowed: O endereço IP de origem não está na lista permitida desta chave.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Erro — rate_limited: Você excedeu o limite de requisições desta chave. Aguarde e tente de novo. | plan_quota_exceeded: A cota diária de requisições do plano de API da oficina acabou. Ela renova às 00:00 UTC; para mais volume, mude de plano.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Erro — internal_error: Erro interno.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Erro — instance_rate_limited: A instância está recebendo requisições demais pela API neste momento. Tente de novo em alguns segundos.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/workshop": {
      "get": {
        "operationId": "getWorkshop",
        "tags": [
          "Geral"
        ],
        "summary": "Dados públicos da oficina",
        "description": "Nome, razão social, CIF (identificação fiscal), endereço, contato, horário semanal da agenda, modo de agendamento, próximos feriados e fuso horário.\n\nQualquer chave válida, sem permissão específica.",
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Resposta bem-sucedida",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "epoch (s)"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Workshop"
                }
              }
            }
          },
          "400": {
            "description": "Erro — bad_request: A requisição não é válida.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Erro — missing_api_key: Falta a chave de API: envie no cabeçalho Authorization: Bearer pt_… ou X-Api-Key. | invalid_api_key: A chave de API não é válida para esta instância.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Erro — ip_not_allowed: O endereço IP de origem não está na lista permitida desta chave.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Erro — rate_limited: Você excedeu o limite de requisições desta chave. Aguarde e tente de novo. | plan_quota_exceeded: A cota diária de requisições do plano de API da oficina acabou. Ela renova às 00:00 UTC; para mais volume, mude de plano.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Erro — internal_error: Erro interno.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Erro — instance_rate_limited: A instância está recebendo requisições demais pela API neste momento. Tente de novo em alguns segundos.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/openapi.json": {
      "get": {
        "operationId": "getOpenApi",
        "tags": [
          "Geral"
        ],
        "summary": "Especificação OpenAPI 3.1",
        "description": "Arquivo gerado a partir deste mesmo catálogo. Sem autenticação. Aceita ?lang=es|en|ca|pt|fr|bg para os textos.\n\nSem autenticação.",
        "security": [],
        "parameters": [
          {
            "name": "lang",
            "in": "query",
            "required": false,
            "description": "Idioma das descrições.",
            "schema": {
              "type": "string",
              "enum": [
                "es",
                "en",
                "ca",
                "pt",
                "fr",
                "bg"
              ],
              "default": "es"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Resposta bem-sucedida",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true,
                  "description": "Documento OpenAPI 3.1"
                }
              }
            }
          },
          "400": {
            "description": "Erro — bad_request: A requisição não é válida.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Erro — rate_limited: Você excedeu o limite de requisições desta chave. Aguarde e tente de novo.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Erro — internal_error: Erro interno.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Erro — instance_rate_limited: A instância está recebendo requisições demais pela API neste momento. Tente de novo em alguns segundos.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/clients": {
      "get": {
        "operationId": "listClients",
        "tags": [
          "Clientes"
        ],
        "summary": "Listar clientes",
        "description": "Ordenados por id crescente. Os clientes apagados a pedido (RGPD) aparecem anonimizados, com erased_at preenchido.\n\nPermissão necessária: `clients:read`.",
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ],
        "x-scope": "clients:read",
        "parameters": [
          {
            "name": "search",
            "in": "query",
            "required": false,
            "description": "Busca em nome, telefone, e-mail e NIF/CIF (mínimo de 2 caracteres).",
            "schema": {
              "type": "string",
              "example": "laura"
            }
          },
          {
            "name": "expand",
            "in": "query",
            "required": false,
            "description": "Relações opcionais a incluir.",
            "schema": {
              "type": "string",
              "enum": [
                "vehicles"
              ]
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Resultados por página (1–200).",
            "schema": {
              "type": "integer",
              "default": 50,
              "example": 50
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "required": false,
            "description": "Cursor opaco devolvido em next_cursor da página anterior.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Resposta bem-sucedida",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "epoch (s)"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data",
                    "next_cursor",
                    "has_more"
                  ],
                  "additionalProperties": false,
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Client"
                      }
                    },
                    "next_cursor": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "description": "Cursor opaco para pedir a próxima página; null se não houver mais",
                      "example": "aWQ6MTIzNA"
                    },
                    "has_more": {
                      "type": "boolean",
                      "description": "true se ainda houver resultados",
                      "example": true
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Erro — bad_request: A requisição não é válida.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Erro — missing_api_key: Falta a chave de API: envie no cabeçalho Authorization: Bearer pt_… ou X-Api-Key. | invalid_api_key: A chave de API não é válida para esta instância.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Erro — ip_not_allowed: O endereço IP de origem não está na lista permitida desta chave. | insufficient_scope: A chave de API não tem a permissão necessária para esta operação. | plan_scope_not_allowed: O plano de API da oficina não inclui esta permissão. Para usá-la é preciso mudar de plano.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Erro — rate_limited: Você excedeu o limite de requisições desta chave. Aguarde e tente de novo. | plan_quota_exceeded: A cota diária de requisições do plano de API da oficina acabou. Ela renova às 00:00 UTC; para mais volume, mude de plano.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Erro — internal_error: Erro interno.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Erro — instance_rate_limited: A instância está recebendo requisições demais pela API neste momento. Tente de novo em alguns segundos.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "post": {
        "operationId": "createClient",
        "tags": [
          "Clientes"
        ],
        "summary": "Criar um cliente",
        "description": "Nunca mescla com um cadastro existente: se o NIF/CIF, o e-mail ou o telefone já estiverem em outro cliente, responde 409 client_exists com existing_id (ou 200 com esse cliente se você passar ?on_conflict=return_existing). O telefone é salvo como no cadastro (Espanha com 9 dígitos, outros países com o código do país) e o e-mail em minúsculas; erros de digitação não são corrigidos. As recusas de marketing ficam no registro de consentimentos.\n\nPermissão necessária: `clients:write`.",
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ],
        "x-scope": "clients:write",
        "parameters": [
          {
            "name": "on_conflict",
            "in": "query",
            "required": false,
            "description": "error (padrão): 409 se já existir. return_existing: 200 com o cliente existente.",
            "schema": {
              "type": "string",
              "enum": [
                "error",
                "return_existing"
              ],
              "default": "error"
            }
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": true,
            "description": "Chave única por operação (UUID recomendado). Repeti-la com o mesmo corpo em 24 h devolve a resposta guardada com Idempotent-Replay: true; com outro corpo, 409 idempotency_conflict.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 255,
              "example": "5f0c7a52-3d1e-4b8a-9c61-2f7e1b0d4a93"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "name": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 200,
                    "description": "Nome completo ou razão social",
                    "example": "Laura Gómez"
                  },
                  "phone": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 30,
                    "description": "Telefone principal. Espanha com 9 dígitos ou com +34; outros países com o código do país (+55…)",
                    "example": "600111222",
                    "pattern": "^[0-9+()\\s.\\-]{6,30}$"
                  },
                  "phone_secondary": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 30,
                    "description": "Segundo telefone",
                    "example": null,
                    "pattern": "^[0-9+()\\s.\\-]{6,30}$"
                  },
                  "email": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 254,
                    "description": "E-mail (não é corrigido: só o formato é validado)",
                    "example": "laura@ejemplo.com",
                    "format": "email"
                  },
                  "billing_email": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 254,
                    "description": "E-mail para as faturas, se for outro",
                    "example": null,
                    "format": "email"
                  },
                  "tax_id": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 20,
                    "description": "NIF/CIF/NIE",
                    "example": "12345678Z",
                    "pattern": "^[A-Za-z0-9\\s.\\-]{3,20}$"
                  },
                  "address": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 300,
                    "example": "C/ Mayor 12"
                  },
                  "city": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 100,
                    "example": "Barcelona"
                  },
                  "zip": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 10,
                    "example": "08001",
                    "pattern": "^[A-Za-z0-9\\s\\-]{3,10}$"
                  },
                  "province": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 100,
                    "example": "Barcelona"
                  },
                  "country": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 60,
                    "example": "ES"
                  },
                  "preferred_language": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "enum": [
                      "es",
                      "ca",
                      "en",
                      "pt",
                      "fr",
                      "bg",
                      null
                    ],
                    "description": "Idioma das comunicações",
                    "example": "es"
                  },
                  "preferred_contact_method": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "enum": [
                      "email",
                      "sms",
                      "whatsapp",
                      "phone",
                      null
                    ],
                    "description": "Canal preferido",
                    "example": "whatsapp"
                  },
                  "marketing_opt_out": {
                    "type": "boolean",
                    "description": "true: não quer comunicações de marketing. Fica no registro de consentimentos",
                    "example": false
                  },
                  "channel_opt_out": {
                    "type": "object",
                    "properties": {
                      "email": {
                        "type": "boolean",
                        "description": "Sem publicidade por e-mail",
                        "example": false
                      },
                      "sms": {
                        "type": "boolean",
                        "description": "Sem publicidade por SMS",
                        "example": false
                      },
                      "whatsapp": {
                        "type": "boolean",
                        "description": "Sem publicidade por WhatsApp",
                        "example": false
                      },
                      "call": {
                        "type": "boolean",
                        "description": "Sem ligações de vendas",
                        "example": false
                      }
                    },
                    "required": [],
                    "additionalProperties": false,
                    "description": "Recusas de marketing por canal (os avisos de serviço não mudam)"
                  }
                },
                "required": [
                  "name"
                ],
                "additionalProperties": false,
                "description": "Cadastro de cliente"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Resposta bem-sucedida",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "epoch (s)"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Client"
                }
              }
            }
          },
          "400": {
            "description": "Erro — bad_request: A requisição não é válida. | idempotency_key_required: Falta o cabeçalho Idempotency-Key (obrigatório em todo POST; de 8 a 255 caracteres visíveis).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Erro — missing_api_key: Falta a chave de API: envie no cabeçalho Authorization: Bearer pt_… ou X-Api-Key. | invalid_api_key: A chave de API não é válida para esta instância.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Erro — ip_not_allowed: O endereço IP de origem não está na lista permitida desta chave. | insufficient_scope: A chave de API não tem a permissão necessária para esta operação. | plan_scope_not_allowed: O plano de API da oficina não inclui esta permissão. Para usá-la é preciso mudar de plano. | test_key_forbidden: Uma chave de teste (pt_test_) não pode fazer operações com efeitos: use uma chave live.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Erro — client_exists: Já existe um cliente com esse telefone, e-mail ou NIF/CIF. | idempotency_conflict: Essa Idempotency-Key já foi usada nas últimas 24 h com outra requisição. | idempotency_in_progress: Há outra requisição com a mesma Idempotency-Key em andamento. Tente de novo em alguns segundos.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "Erro — validation_error: O corpo da requisição não é válido: confira a lista «fields».",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ValidationError"
                }
              }
            }
          },
          "429": {
            "description": "Erro — rate_limited: Você excedeu o limite de requisições desta chave. Aguarde e tente de novo. | plan_quota_exceeded: A cota diária de requisições do plano de API da oficina acabou. Ela renova às 00:00 UTC; para mais volume, mude de plano.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Erro — internal_error: Erro interno.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Erro — instance_rate_limited: A instância está recebendo requisições demais pela API neste momento. Tente de novo em alguns segundos.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/clients/{id}": {
      "get": {
        "operationId": "getClient",
        "tags": [
          "Clientes"
        ],
        "summary": "Detalhe de um cliente",
        "description": "Permissão necessária: `clients:read`.",
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ],
        "x-scope": "clients:read",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Id do cliente.",
            "schema": {
              "type": "integer",
              "example": 1234
            }
          },
          {
            "name": "expand",
            "in": "query",
            "required": false,
            "description": "Relações opcionais a incluir.",
            "schema": {
              "type": "string",
              "enum": [
                "vehicles"
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Resposta bem-sucedida",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "epoch (s)"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Client"
                }
              }
            }
          },
          "400": {
            "description": "Erro — bad_request: A requisição não é válida.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Erro — missing_api_key: Falta a chave de API: envie no cabeçalho Authorization: Bearer pt_… ou X-Api-Key. | invalid_api_key: A chave de API não é válida para esta instância.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Erro — ip_not_allowed: O endereço IP de origem não está na lista permitida desta chave. | insufficient_scope: A chave de API não tem a permissão necessária para esta operação. | plan_scope_not_allowed: O plano de API da oficina não inclui esta permissão. Para usá-la é preciso mudar de plano.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Erro — not_found: Recurso não encontrado.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Erro — rate_limited: Você excedeu o limite de requisições desta chave. Aguarde e tente de novo. | plan_quota_exceeded: A cota diária de requisições do plano de API da oficina acabou. Ela renova às 00:00 UTC; para mais volume, mude de plano.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Erro — internal_error: Erro interno.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Erro — instance_rate_limited: A instância está recebendo requisições demais pela API neste momento. Tente de novo em alguns segundos.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "patch": {
        "operationId": "updateClient",
        "tags": [
          "Clientes"
        ],
        "summary": "Modificar um cliente",
        "description": "Só mudam os campos enviados (null limpa o campo). É possível mudar e-mail ou telefone, e fica o valor anterior → novo na atividade do cliente com o nome da chave; emite client.updated. Não é possível usar o e-mail, telefone ou NIF de OUTRO cadastro (409 client_exists). Cadastros apagados a pedido (RGPD) respondem 409 client_erased.\n\nPermissão necessária: `clients:write`.",
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ],
        "x-scope": "clients:write",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Id do cliente.",
            "schema": {
              "type": "integer",
              "example": 1234
            }
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Chave única por operação (UUID recomendado). Repeti-la com o mesmo corpo em 24 h devolve a resposta guardada com Idempotent-Replay: true; com outro corpo, 409 idempotency_conflict.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 255,
              "example": "5f0c7a52-3d1e-4b8a-9c61-2f7e1b0d4a93"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "name": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 200,
                    "description": "Nome completo ou razão social",
                    "example": "Laura Gómez Ruiz"
                  },
                  "phone": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 30,
                    "description": "Telefone principal. Espanha com 9 dígitos ou com +34; outros países com o código do país (+55…)",
                    "example": "600111222",
                    "pattern": "^[0-9+()\\s.\\-]{6,30}$"
                  },
                  "phone_secondary": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 30,
                    "description": "Segundo telefone",
                    "example": null,
                    "pattern": "^[0-9+()\\s.\\-]{6,30}$"
                  },
                  "email": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 254,
                    "description": "E-mail (não é corrigido: só o formato é validado)",
                    "example": "laura@ejemplo.com",
                    "format": "email"
                  },
                  "billing_email": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 254,
                    "description": "E-mail para as faturas, se for outro",
                    "example": null,
                    "format": "email"
                  },
                  "tax_id": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 20,
                    "description": "NIF/CIF/NIE",
                    "example": "12345678Z",
                    "pattern": "^[A-Za-z0-9\\s.\\-]{3,20}$"
                  },
                  "address": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 300,
                    "example": "C/ Mayor 12"
                  },
                  "city": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 100,
                    "example": "Barcelona"
                  },
                  "zip": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 10,
                    "example": "08001",
                    "pattern": "^[A-Za-z0-9\\s\\-]{3,10}$"
                  },
                  "province": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 100,
                    "example": "Barcelona"
                  },
                  "country": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 60,
                    "example": "ES"
                  },
                  "preferred_language": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "enum": [
                      "es",
                      "ca",
                      "en",
                      "pt",
                      "fr",
                      "bg",
                      null
                    ],
                    "description": "Idioma das comunicações",
                    "example": "es"
                  },
                  "preferred_contact_method": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "enum": [
                      "email",
                      "sms",
                      "whatsapp",
                      "phone",
                      null
                    ],
                    "description": "Canal preferido",
                    "example": "whatsapp"
                  },
                  "marketing_opt_out": {
                    "type": "boolean",
                    "description": "true: não quer comunicações de marketing. Fica no registro de consentimentos",
                    "example": false
                  },
                  "channel_opt_out": {
                    "type": "object",
                    "properties": {
                      "email": {
                        "type": "boolean",
                        "description": "Sem publicidade por e-mail",
                        "example": false
                      },
                      "sms": {
                        "type": "boolean",
                        "description": "Sem publicidade por SMS",
                        "example": false
                      },
                      "whatsapp": {
                        "type": "boolean",
                        "description": "Sem publicidade por WhatsApp",
                        "example": false
                      },
                      "call": {
                        "type": "boolean",
                        "description": "Sem ligações de vendas",
                        "example": false
                      }
                    },
                    "required": [],
                    "additionalProperties": false,
                    "description": "Recusas de marketing por canal (os avisos de serviço não mudam)"
                  }
                },
                "required": [],
                "additionalProperties": false,
                "description": "Alterações parciais: só os campos enviados. null limpa o campo"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Resposta bem-sucedida",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "epoch (s)"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Client"
                }
              }
            }
          },
          "400": {
            "description": "Erro — bad_request: A requisição não é válida.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Erro — missing_api_key: Falta a chave de API: envie no cabeçalho Authorization: Bearer pt_… ou X-Api-Key. | invalid_api_key: A chave de API não é válida para esta instância.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Erro — ip_not_allowed: O endereço IP de origem não está na lista permitida desta chave. | insufficient_scope: A chave de API não tem a permissão necessária para esta operação. | plan_scope_not_allowed: O plano de API da oficina não inclui esta permissão. Para usá-la é preciso mudar de plano. | test_key_forbidden: Uma chave de teste (pt_test_) não pode fazer operações com efeitos: use uma chave live.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Erro — not_found: Recurso não encontrado.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Erro — client_exists: Já existe um cliente com esse telefone, e-mail ou NIF/CIF. | client_erased: O cliente pediu a exclusão dos seus dados (RGPD): o cadastro não aceita alterações nem registros vinculados.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "Erro — validation_error: O corpo da requisição não é válido: confira a lista «fields».",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ValidationError"
                }
              }
            }
          },
          "429": {
            "description": "Erro — rate_limited: Você excedeu o limite de requisições desta chave. Aguarde e tente de novo. | plan_quota_exceeded: A cota diária de requisições do plano de API da oficina acabou. Ela renova às 00:00 UTC; para mais volume, mude de plano.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Erro — internal_error: Erro interno.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Erro — instance_rate_limited: A instância está recebendo requisições demais pela API neste momento. Tente de novo em alguns segundos.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/vehicles": {
      "get": {
        "operationId": "listVehicles",
        "tags": [
          "Veículos"
        ],
        "summary": "Listar veículos",
        "description": "Inclui a última quilometragem registrada e o vencimento da inspeção técnica (ITV).\n\nPermissão necessária: `vehicles:read`.",
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ],
        "x-scope": "vehicles:read",
        "parameters": [
          {
            "name": "plate",
            "in": "query",
            "required": false,
            "description": "Placa exata (espaços e hifens são ignorados).",
            "schema": {
              "type": "string",
              "example": "1234KLM"
            }
          },
          {
            "name": "client_id",
            "in": "query",
            "required": false,
            "description": "Filtrar por cliente.",
            "schema": {
              "type": "integer",
              "example": 1204
            }
          },
          {
            "name": "status",
            "in": "query",
            "required": false,
            "description": "Status do veículo.",
            "schema": {
              "type": "string",
              "enum": [
                "activo",
                "baja_temporal",
                "baja"
              ]
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Resultados por página (1–200).",
            "schema": {
              "type": "integer",
              "default": 50,
              "example": 50
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "required": false,
            "description": "Cursor opaco devolvido em next_cursor da página anterior.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Resposta bem-sucedida",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "epoch (s)"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data",
                    "next_cursor",
                    "has_more"
                  ],
                  "additionalProperties": false,
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Vehicle"
                      }
                    },
                    "next_cursor": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "description": "Cursor opaco para pedir a próxima página; null se não houver mais",
                      "example": "aWQ6MTIzNA"
                    },
                    "has_more": {
                      "type": "boolean",
                      "description": "true se ainda houver resultados",
                      "example": true
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Erro — bad_request: A requisição não é válida.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Erro — missing_api_key: Falta a chave de API: envie no cabeçalho Authorization: Bearer pt_… ou X-Api-Key. | invalid_api_key: A chave de API não é válida para esta instância.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Erro — ip_not_allowed: O endereço IP de origem não está na lista permitida desta chave. | insufficient_scope: A chave de API não tem a permissão necessária para esta operação. | plan_scope_not_allowed: O plano de API da oficina não inclui esta permissão. Para usá-la é preciso mudar de plano.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Erro — rate_limited: Você excedeu o limite de requisições desta chave. Aguarde e tente de novo. | plan_quota_exceeded: A cota diária de requisições do plano de API da oficina acabou. Ela renova às 00:00 UTC; para mais volume, mude de plano.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Erro — internal_error: Erro interno.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Erro — instance_rate_limited: A instância está recebendo requisições demais pela API neste momento. Tente de novo em alguns segundos.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "post": {
        "operationId": "createVehicle",
        "tags": [
          "Veículos"
        ],
        "summary": "Cadastrar um veículo",
        "description": "Sempre em nome de um cliente existente (client_id). A placa é normalizada (maiúsculas, sem espaços nem hifens). Se já estiver no cadastro de OUTRO cliente: 409 vehicle_belongs_to_other_client. Se o mesmo cliente já a tiver: 409 vehicle_exists com existing_id (ou 200 com ?on_conflict=return_existing).\n\nPermissão necessária: `vehicles:write`.",
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ],
        "x-scope": "vehicles:write",
        "parameters": [
          {
            "name": "on_conflict",
            "in": "query",
            "required": false,
            "description": "error (padrão) ou return_existing.",
            "schema": {
              "type": "string",
              "enum": [
                "error",
                "return_existing"
              ],
              "default": "error"
            }
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": true,
            "description": "Chave única por operação (UUID recomendado). Repeti-la com o mesmo corpo em 24 h devolve a resposta guardada com Idempotent-Replay: true; com outro corpo, 409 idempotency_conflict.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 255,
              "example": "5f0c7a52-3d1e-4b8a-9c61-2f7e1b0d4a93"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "client_id": {
                    "type": "integer",
                    "minimum": 1,
                    "description": "Cliente proprietário (deve existir)",
                    "example": 1204
                  },
                  "plate": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 15,
                    "description": "Placa; é salva em maiúsculas sem espaços nem hifens",
                    "example": "1234 KLM",
                    "pattern": "^[A-Za-z0-9\\s.\\-]{2,15}$"
                  },
                  "vin": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 17,
                    "description": "Bastidor",
                    "example": "VSSZZZ5FZJR123456",
                    "pattern": "^[A-Za-z0-9]{11,17}$"
                  },
                  "brand": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 60,
                    "example": "Seat"
                  },
                  "model": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 80,
                    "example": "León"
                  },
                  "variant": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 120,
                    "example": "1.5 TSI"
                  },
                  "year": {
                    "type": [
                      "integer",
                      "null"
                    ],
                    "minimum": 1900,
                    "maximum": 2100,
                    "example": 2019
                  },
                  "registration_date": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "date",
                    "example": "2019-03-15"
                  },
                  "fuel": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 30,
                    "example": "Gasolina"
                  },
                  "transmission": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 30,
                    "example": "Manual"
                  },
                  "engine_code": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 30,
                    "example": "DADA"
                  },
                  "horsepower": {
                    "type": [
                      "integer",
                      "null"
                    ],
                    "minimum": 1,
                    "maximum": 2000,
                    "example": 130
                  },
                  "displacement": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 10,
                    "description": "Cilindrada (cc)",
                    "example": "1498",
                    "pattern": "^[0-9.,]{1,10}$"
                  },
                  "color_code": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 30,
                    "example": null
                  },
                  "environmental_label": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 10,
                    "description": "Selo ambiental DGT da Espanha (0, ECO, C, B)",
                    "example": "C"
                  },
                  "itv_expiry_date": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "date",
                    "example": "2027-03-15"
                  },
                  "status": {
                    "type": "string",
                    "enum": [
                      "activo",
                      "baja_temporal",
                      "baja"
                    ],
                    "example": "activo"
                  }
                },
                "required": [
                  "client_id",
                  "plate"
                ],
                "additionalProperties": false,
                "description": "Cadastro de veículo de um cliente"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Resposta bem-sucedida",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "epoch (s)"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Vehicle"
                }
              }
            }
          },
          "400": {
            "description": "Erro — bad_request: A requisição não é válida. | idempotency_key_required: Falta o cabeçalho Idempotency-Key (obrigatório em todo POST; de 8 a 255 caracteres visíveis).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Erro — missing_api_key: Falta a chave de API: envie no cabeçalho Authorization: Bearer pt_… ou X-Api-Key. | invalid_api_key: A chave de API não é válida para esta instância.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Erro — ip_not_allowed: O endereço IP de origem não está na lista permitida desta chave. | insufficient_scope: A chave de API não tem a permissão necessária para esta operação. | plan_scope_not_allowed: O plano de API da oficina não inclui esta permissão. Para usá-la é preciso mudar de plano. | test_key_forbidden: Uma chave de teste (pt_test_) não pode fazer operações com efeitos: use uma chave live.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Erro — vehicle_exists: Esse cliente já tem um veículo com essa placa. | vehicle_belongs_to_other_client: Essa placa já está cadastrada em nome de outro cliente. | client_erased: O cliente pediu a exclusão dos seus dados (RGPD): o cadastro não aceita alterações nem registros vinculados. | idempotency_conflict: Essa Idempotency-Key já foi usada nas últimas 24 h com outra requisição. | idempotency_in_progress: Há outra requisição com a mesma Idempotency-Key em andamento. Tente de novo em alguns segundos.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "Erro — validation_error: O corpo da requisição não é válido: confira a lista «fields».",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ValidationError"
                }
              }
            }
          },
          "429": {
            "description": "Erro — rate_limited: Você excedeu o limite de requisições desta chave. Aguarde e tente de novo. | plan_quota_exceeded: A cota diária de requisições do plano de API da oficina acabou. Ela renova às 00:00 UTC; para mais volume, mude de plano.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Erro — internal_error: Erro interno.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Erro — instance_rate_limited: A instância está recebendo requisições demais pela API neste momento. Tente de novo em alguns segundos.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/vehicles/{id}": {
      "get": {
        "operationId": "getVehicle",
        "tags": [
          "Veículos"
        ],
        "summary": "Detalhe de um veículo",
        "description": "Permissão necessária: `vehicles:read`.",
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ],
        "x-scope": "vehicles:read",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Id do veículo.",
            "schema": {
              "type": "integer",
              "example": 1234
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Resposta bem-sucedida",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "epoch (s)"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Vehicle"
                }
              }
            }
          },
          "400": {
            "description": "Erro — bad_request: A requisição não é válida.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Erro — missing_api_key: Falta a chave de API: envie no cabeçalho Authorization: Bearer pt_… ou X-Api-Key. | invalid_api_key: A chave de API não é válida para esta instância.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Erro — ip_not_allowed: O endereço IP de origem não está na lista permitida desta chave. | insufficient_scope: A chave de API não tem a permissão necessária para esta operação. | plan_scope_not_allowed: O plano de API da oficina não inclui esta permissão. Para usá-la é preciso mudar de plano.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Erro — not_found: Recurso não encontrado.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Erro — rate_limited: Você excedeu o limite de requisições desta chave. Aguarde e tente de novo. | plan_quota_exceeded: A cota diária de requisições do plano de API da oficina acabou. Ela renova às 00:00 UTC; para mais volume, mude de plano.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Erro — internal_error: Erro interno.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Erro — instance_rate_limited: A instância está recebendo requisições demais pela API neste momento. Tente de novo em alguns segundos.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "patch": {
        "operationId": "updateVehicle",
        "tags": [
          "Veículos"
        ],
        "summary": "Modificar um veículo",
        "description": "Alterações parciais. client_id não aceita null: um veículo nunca é desvinculado do proprietário pela API (mas pode passar para outro cliente existente).\n\nPermissão necessária: `vehicles:write`.",
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ],
        "x-scope": "vehicles:write",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Id do veículo.",
            "schema": {
              "type": "integer",
              "example": 1234
            }
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Chave única por operação (UUID recomendado). Repeti-la com o mesmo corpo em 24 h devolve a resposta guardada com Idempotent-Replay: true; com outro corpo, 409 idempotency_conflict.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 255,
              "example": "5f0c7a52-3d1e-4b8a-9c61-2f7e1b0d4a93"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "client_id": {
                    "type": "integer",
                    "minimum": 1,
                    "description": "Cliente proprietário (deve existir)",
                    "example": 1204
                  },
                  "plate": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 15,
                    "description": "Placa; é salva em maiúsculas sem espaços nem hifens",
                    "example": "1234 KLM",
                    "pattern": "^[A-Za-z0-9\\s.\\-]{2,15}$"
                  },
                  "vin": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 17,
                    "description": "Bastidor",
                    "example": "VSSZZZ5FZJR123456",
                    "pattern": "^[A-Za-z0-9]{11,17}$"
                  },
                  "brand": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 60,
                    "example": "Seat"
                  },
                  "model": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 80,
                    "example": "León"
                  },
                  "variant": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 120,
                    "example": "1.5 TSI"
                  },
                  "year": {
                    "type": [
                      "integer",
                      "null"
                    ],
                    "minimum": 1900,
                    "maximum": 2100,
                    "example": 2019
                  },
                  "registration_date": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "date",
                    "example": "2019-03-15"
                  },
                  "fuel": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 30,
                    "example": "Gasolina"
                  },
                  "transmission": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 30,
                    "example": "Manual"
                  },
                  "engine_code": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 30,
                    "example": "DADA"
                  },
                  "horsepower": {
                    "type": [
                      "integer",
                      "null"
                    ],
                    "minimum": 1,
                    "maximum": 2000,
                    "example": 130
                  },
                  "displacement": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 10,
                    "description": "Cilindrada (cc)",
                    "example": "1498",
                    "pattern": "^[0-9.,]{1,10}$"
                  },
                  "color_code": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 30,
                    "example": null
                  },
                  "environmental_label": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 10,
                    "description": "Selo ambiental DGT da Espanha (0, ECO, C, B)",
                    "example": "C"
                  },
                  "itv_expiry_date": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "date",
                    "example": "2027-03-15"
                  },
                  "status": {
                    "type": "string",
                    "enum": [
                      "activo",
                      "baja_temporal",
                      "baja"
                    ],
                    "example": "activo"
                  }
                },
                "required": [],
                "additionalProperties": false,
                "description": "Alterações parciais do veículo"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Resposta bem-sucedida",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "epoch (s)"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Vehicle"
                }
              }
            }
          },
          "400": {
            "description": "Erro — bad_request: A requisição não é válida.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Erro — missing_api_key: Falta a chave de API: envie no cabeçalho Authorization: Bearer pt_… ou X-Api-Key. | invalid_api_key: A chave de API não é válida para esta instância.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Erro — ip_not_allowed: O endereço IP de origem não está na lista permitida desta chave. | insufficient_scope: A chave de API não tem a permissão necessária para esta operação. | plan_scope_not_allowed: O plano de API da oficina não inclui esta permissão. Para usá-la é preciso mudar de plano. | test_key_forbidden: Uma chave de teste (pt_test_) não pode fazer operações com efeitos: use uma chave live.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Erro — not_found: Recurso não encontrado.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Erro — vehicle_exists: Esse cliente já tem um veículo com essa placa. | vehicle_belongs_to_other_client: Essa placa já está cadastrada em nome de outro cliente. | client_erased: O cliente pediu a exclusão dos seus dados (RGPD): o cadastro não aceita alterações nem registros vinculados.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "Erro — validation_error: O corpo da requisição não é válido: confira a lista «fields».",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ValidationError"
                }
              }
            }
          },
          "429": {
            "description": "Erro — rate_limited: Você excedeu o limite de requisições desta chave. Aguarde e tente de novo. | plan_quota_exceeded: A cota diária de requisições do plano de API da oficina acabou. Ela renova às 00:00 UTC; para mais volume, mude de plano.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Erro — internal_error: Erro interno.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Erro — instance_rate_limited: A instância está recebendo requisições demais pela API neste momento. Tente de novo em alguns segundos.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/budgets": {
      "get": {
        "operationId": "listBudgets",
        "tags": [
          "Orçamentos"
        ],
        "summary": "Listar orçamentos",
        "description": "Nunca inclui os orçamentos de uso interno da oficina nem dados de custo. O detalhe (com itens e totais) está em /budgets/{id}.\n\nPermissão necessária: `budgets:read`.",
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ],
        "x-scope": "budgets:read",
        "parameters": [
          {
            "name": "status",
            "in": "query",
            "required": false,
            "description": "Status exato, com o valor em espanhol (Pendiente, Enviado, Aprobado, Finalizado, Facturado, Rechazado…).",
            "schema": {
              "type": "string",
              "example": "Aprobado"
            }
          },
          {
            "name": "client_id",
            "in": "query",
            "required": false,
            "description": "Filtrar por cliente.",
            "schema": {
              "type": "integer",
              "example": 1204
            }
          },
          {
            "name": "vehicle_id",
            "in": "query",
            "required": false,
            "description": "Filtrar por veículo.",
            "schema": {
              "type": "integer",
              "example": 871
            }
          },
          {
            "name": "from",
            "in": "query",
            "required": false,
            "description": "Criados a partir desta data.",
            "schema": {
              "type": "string",
              "format": "date-time",
              "example": "2026-10-01"
            }
          },
          {
            "name": "to",
            "in": "query",
            "required": false,
            "description": "Criados até esta data.",
            "schema": {
              "type": "string",
              "format": "date-time",
              "example": "2026-10-31"
            }
          },
          {
            "name": "updated_since",
            "in": "query",
            "required": false,
            "description": "Só registros modificados a partir desta data (ISO 8601).",
            "schema": {
              "type": "string",
              "format": "date-time",
              "example": "2026-10-01T00:00:00Z"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Resultados por página (1–200).",
            "schema": {
              "type": "integer",
              "default": 50,
              "example": 50
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "required": false,
            "description": "Cursor opaco devolvido em next_cursor da página anterior.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Resposta bem-sucedida",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "epoch (s)"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data",
                    "next_cursor",
                    "has_more"
                  ],
                  "additionalProperties": false,
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Budget"
                      }
                    },
                    "next_cursor": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "description": "Cursor opaco para pedir a próxima página; null se não houver mais",
                      "example": "aWQ6MTIzNA"
                    },
                    "has_more": {
                      "type": "boolean",
                      "description": "true se ainda houver resultados",
                      "example": true
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Erro — bad_request: A requisição não é válida.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Erro — missing_api_key: Falta a chave de API: envie no cabeçalho Authorization: Bearer pt_… ou X-Api-Key. | invalid_api_key: A chave de API não é válida para esta instância.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Erro — ip_not_allowed: O endereço IP de origem não está na lista permitida desta chave. | insufficient_scope: A chave de API não tem a permissão necessária para esta operação. | plan_scope_not_allowed: O plano de API da oficina não inclui esta permissão. Para usá-la é preciso mudar de plano.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Erro — rate_limited: Você excedeu o limite de requisições desta chave. Aguarde e tente de novo. | plan_quota_exceeded: A cota diária de requisições do plano de API da oficina acabou. Ela renova às 00:00 UTC; para mais volume, mude de plano.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Erro — internal_error: Erro interno.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Erro — instance_rate_limited: A instância está recebendo requisições demais pela API neste momento. Tente de novo em alguns segundos.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "post": {
        "operationId": "createBudget",
        "tags": [
          "Orçamentos"
        ],
        "summary": "Criar um orçamento",
        "description": "Entra como «Pendiente» com canal «API», igual a um cadastro feito no programa (registro, marco de abertura, webhook lead.created). Os totais são calculados no servidor com o imposto da oficina; se um item não trouxer tax_rate, usa o da oficina. O veículo, se informado, deve ser do cliente. Não aceita categorias de uso interno. Com notify_client=true (e permissão communications:send) o cliente recebe a confirmação com o link de acompanhamento.\n\nPermissão necessária: `budgets:write`.",
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ],
        "x-scope": "budgets:write",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": true,
            "description": "Chave única por operação (UUID recomendado). Repeti-la com o mesmo corpo em 24 h devolve a resposta guardada com Idempotent-Replay: true; com outro corpo, 409 idempotency_conflict.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 255,
              "example": "5f0c7a52-3d1e-4b8a-9c61-2f7e1b0d4a93"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "client_id": {
                    "type": "integer",
                    "minimum": 1,
                    "description": "Cliente (deve existir)",
                    "example": 1204
                  },
                  "vehicle_id": {
                    "type": [
                      "integer",
                      "null"
                    ],
                    "minimum": 1,
                    "description": "Veículo do cliente",
                    "example": 871
                  },
                  "category_id": {
                    "type": [
                      "integer",
                      "null"
                    ],
                    "minimum": 1,
                    "description": "Categoria de serviço (GET /catalog/services)",
                    "example": 5
                  },
                  "subcategory_id": {
                    "type": [
                      "integer",
                      "null"
                    ],
                    "minimum": 1,
                    "description": "Subcategoria dessa categoria",
                    "example": 51
                  },
                  "client_reference": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 100,
                    "description": "Referência do cliente (pedido, sinistro…) que aparecerá na fatura",
                    "example": "PED-2026-118"
                  },
                  "public_notes": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 5000,
                    "description": "Observações visíveis para o cliente",
                    "example": "Verificar também o ruído da suspensão."
                  },
                  "lines": {
                    "type": "array",
                    "maxItems": 200,
                    "items": {
                      "type": "object",
                      "properties": {
                        "description": {
                          "type": "string",
                          "minLength": 1,
                          "maxLength": 2000,
                          "description": "Descrição visível para o cliente",
                          "example": "Troca de óleo e filtro"
                        },
                        "quantity": {
                          "type": "number",
                          "minimum": 0,
                          "maximum": 100000,
                          "description": "Quantidade (horas na mão de obra)",
                          "example": 1
                        },
                        "unit_price": {
                          "type": "number",
                          "minimum": -1000000,
                          "maximum": 1000000,
                          "description": "Preço unitário de venda sem impostos, em euros",
                          "example": 65
                        },
                        "tax_rate": {
                          "type": [
                            "number",
                            "null"
                          ],
                          "minimum": 0,
                          "maximum": 30,
                          "description": "Alíquota de imposto; se omitida, a da oficina (IVA/IGIC/IPSI conforme a região)",
                          "example": 21
                        },
                        "discount_pct": {
                          "type": "number",
                          "minimum": 0,
                          "maximum": 100,
                          "description": "Desconto em %",
                          "example": 0
                        },
                        "line_type": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "enum": [
                            "labor",
                            "diagnosis",
                            "materials",
                            "parts",
                            "pieces",
                            "storage",
                            "other",
                            null
                          ],
                          "example": "labor"
                        },
                        "reference": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "maxLength": 100,
                          "description": "Código da peça",
                          "example": null
                        },
                        "group_title": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "maxLength": 200,
                          "description": "Título do grupo a que pertence",
                          "example": null
                        }
                      },
                      "required": [
                        "description",
                        "quantity",
                        "unit_price"
                      ],
                      "additionalProperties": false,
                      "description": "Item novo"
                    },
                    "description": "Itens iniciais; os totais são calculados pelo servidor"
                  },
                  "notify_client": {
                    "type": "boolean",
                    "description": "Enviar ao cliente a confirmação com o link de acompanhamento. Também exige communications:send",
                    "example": false
                  }
                },
                "required": [
                  "client_id"
                ],
                "additionalProperties": false,
                "description": "Orçamento novo; entra no status «Pendiente» com canal «API»"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Resposta bem-sucedida",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "epoch (s)"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/BudgetDetail"
                }
              }
            }
          },
          "400": {
            "description": "Erro — bad_request: A requisição não é válida. | idempotency_key_required: Falta o cabeçalho Idempotency-Key (obrigatório em todo POST; de 8 a 255 caracteres visíveis).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Erro — missing_api_key: Falta a chave de API: envie no cabeçalho Authorization: Bearer pt_… ou X-Api-Key. | invalid_api_key: A chave de API não é válida para esta instância.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Erro — ip_not_allowed: O endereço IP de origem não está na lista permitida desta chave. | insufficient_scope: A chave de API não tem a permissão necessária para esta operação. | plan_scope_not_allowed: O plano de API da oficina não inclui esta permissão. Para usá-la é preciso mudar de plano. | test_key_forbidden: Uma chave de teste (pt_test_) não pode fazer operações com efeitos: use uma chave live.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Erro — vehicle_belongs_to_other_client: Essa placa já está cadastrada em nome de outro cliente. | client_erased: O cliente pediu a exclusão dos seus dados (RGPD): o cadastro não aceita alterações nem registros vinculados. | idempotency_conflict: Essa Idempotency-Key já foi usada nas últimas 24 h com outra requisição. | idempotency_in_progress: Há outra requisição com a mesma Idempotency-Key em andamento. Tente de novo em alguns segundos.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "Erro — validation_error: O corpo da requisição não é válido: confira a lista «fields».",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ValidationError"
                }
              }
            }
          },
          "429": {
            "description": "Erro — rate_limited: Você excedeu o limite de requisições desta chave. Aguarde e tente de novo. | plan_quota_exceeded: A cota diária de requisições do plano de API da oficina acabou. Ela renova às 00:00 UTC; para mais volume, mude de plano.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Erro — internal_error: Erro interno.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Erro — instance_rate_limited: A instância está recebendo requisições demais pela API neste momento. Tente de novo em alguns segundos.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/budgets/{id}": {
      "get": {
        "operationId": "getBudget",
        "tags": [
          "Orçamentos"
        ],
        "summary": "Detalhe de um orçamento",
        "description": "Itens (descrição, quantidade, preço unitário, imposto, tipo, desconto), totais detalhados, agendamento, datas de entrada e saída, responsável e URL pública de acompanhamento.\n\nPermissão necessária: `budgets:read`.",
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ],
        "x-scope": "budgets:read",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Id do orçamento.",
            "schema": {
              "type": "integer",
              "example": 1234
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Resposta bem-sucedida",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "epoch (s)"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/BudgetDetail"
                }
              }
            }
          },
          "400": {
            "description": "Erro — bad_request: A requisição não é válida.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Erro — missing_api_key: Falta a chave de API: envie no cabeçalho Authorization: Bearer pt_… ou X-Api-Key. | invalid_api_key: A chave de API não é válida para esta instância.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Erro — ip_not_allowed: O endereço IP de origem não está na lista permitida desta chave. | insufficient_scope: A chave de API não tem a permissão necessária para esta operação. | plan_scope_not_allowed: O plano de API da oficina não inclui esta permissão. Para usá-la é preciso mudar de plano.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Erro — not_found: Recurso não encontrado.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Erro — rate_limited: Você excedeu o limite de requisições desta chave. Aguarde e tente de novo. | plan_quota_exceeded: A cota diária de requisições do plano de API da oficina acabou. Ela renova às 00:00 UTC; para mais volume, mude de plano.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Erro — internal_error: Erro interno.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Erro — instance_rate_limited: A instância está recebendo requisições demais pela API neste momento. Tente de novo em alguns segundos.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/budgets/{id}/lines": {
      "post": {
        "operationId": "addBudgetLine",
        "tags": [
          "Orçamentos"
        ],
        "summary": "Adicionar um item",
        "description": "Os demais itens mantêm o id. Deixa um instantâneo anterior e registro como qualquer edição no programa. 409 budget_locked se o orçamento estiver Facturado, Facturado externamente, Cancelado, Rechazado, Desistido ou já tiver fatura.\n\nPermissão necessária: `budgets:write`.",
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ],
        "x-scope": "budgets:write",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Id do orçamento.",
            "schema": {
              "type": "integer",
              "example": 1234
            }
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": true,
            "description": "Chave única por operação (UUID recomendado). Repeti-la com o mesmo corpo em 24 h devolve a resposta guardada com Idempotent-Replay: true; com outro corpo, 409 idempotency_conflict.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 255,
              "example": "5f0c7a52-3d1e-4b8a-9c61-2f7e1b0d4a93"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "description": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 2000,
                    "description": "Descrição visível para o cliente",
                    "example": "Troca de óleo e filtro"
                  },
                  "quantity": {
                    "type": "number",
                    "minimum": 0,
                    "maximum": 100000,
                    "description": "Quantidade (horas na mão de obra)",
                    "example": 1
                  },
                  "unit_price": {
                    "type": "number",
                    "minimum": -1000000,
                    "maximum": 1000000,
                    "description": "Preço unitário de venda sem impostos, em euros",
                    "example": 65
                  },
                  "tax_rate": {
                    "type": [
                      "number",
                      "null"
                    ],
                    "minimum": 0,
                    "maximum": 30,
                    "description": "Alíquota de imposto; se omitida, a da oficina (IVA/IGIC/IPSI conforme a região)",
                    "example": 21
                  },
                  "discount_pct": {
                    "type": "number",
                    "minimum": 0,
                    "maximum": 100,
                    "description": "Desconto em %",
                    "example": 0
                  },
                  "line_type": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "enum": [
                      "labor",
                      "diagnosis",
                      "materials",
                      "parts",
                      "pieces",
                      "storage",
                      "other",
                      null
                    ],
                    "example": "labor"
                  },
                  "reference": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 100,
                    "description": "Código da peça",
                    "example": null
                  },
                  "group_title": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 200,
                    "description": "Título do grupo a que pertence",
                    "example": null
                  }
                },
                "required": [
                  "description",
                  "quantity",
                  "unit_price"
                ],
                "additionalProperties": false,
                "description": "Item novo"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Resposta bem-sucedida",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "epoch (s)"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "line",
                    "budget"
                  ],
                  "additionalProperties": false,
                  "description": "Item criado e orçamento com os totais recalculados",
                  "properties": {
                    "line": {
                      "$ref": "#/components/schemas/BudgetLine"
                    },
                    "budget": {
                      "$ref": "#/components/schemas/BudgetDetail"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Erro — bad_request: A requisição não é válida. | idempotency_key_required: Falta o cabeçalho Idempotency-Key (obrigatório em todo POST; de 8 a 255 caracteres visíveis).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Erro — missing_api_key: Falta a chave de API: envie no cabeçalho Authorization: Bearer pt_… ou X-Api-Key. | invalid_api_key: A chave de API não é válida para esta instância.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Erro — ip_not_allowed: O endereço IP de origem não está na lista permitida desta chave. | insufficient_scope: A chave de API não tem a permissão necessária para esta operação. | plan_scope_not_allowed: O plano de API da oficina não inclui esta permissão. Para usá-la é preciso mudar de plano. | test_key_forbidden: Uma chave de teste (pt_test_) não pode fazer operações com efeitos: use uma chave live.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Erro — not_found: Recurso não encontrado.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Erro — budget_locked: O orçamento está fechado e não aceita mais essa alteração. | idempotency_conflict: Essa Idempotency-Key já foi usada nas últimas 24 h com outra requisição. | idempotency_in_progress: Há outra requisição com a mesma Idempotency-Key em andamento. Tente de novo em alguns segundos.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "Erro — validation_error: O corpo da requisição não é válido: confira a lista «fields».",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ValidationError"
                }
              }
            }
          },
          "429": {
            "description": "Erro — rate_limited: Você excedeu o limite de requisições desta chave. Aguarde e tente de novo. | plan_quota_exceeded: A cota diária de requisições do plano de API da oficina acabou. Ela renova às 00:00 UTC; para mais volume, mude de plano.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Erro — internal_error: Erro interno.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Erro — instance_rate_limited: A instância está recebendo requisições demais pela API neste momento. Tente de novo em alguns segundos.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/budgets/{id}/lines/{lineId}": {
      "patch": {
        "operationId": "updateBudgetLine",
        "tags": [
          "Orçamentos"
        ],
        "summary": "Modificar um item",
        "description": "Permissão necessária: `budgets:write`.",
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ],
        "x-scope": "budgets:write",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Id do orçamento.",
            "schema": {
              "type": "integer",
              "example": 1234
            }
          },
          {
            "name": "lineId",
            "in": "path",
            "required": true,
            "description": "Id do item.",
            "schema": {
              "type": "integer",
              "example": 5501
            }
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Chave única por operação (UUID recomendado). Repeti-la com o mesmo corpo em 24 h devolve a resposta guardada com Idempotent-Replay: true; com outro corpo, 409 idempotency_conflict.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 255,
              "example": "5f0c7a52-3d1e-4b8a-9c61-2f7e1b0d4a93"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "description": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 2000,
                    "description": "Descrição visível para o cliente",
                    "example": "Troca de óleo e filtro"
                  },
                  "quantity": {
                    "type": "number",
                    "minimum": 0,
                    "maximum": 100000,
                    "description": "Quantidade (horas na mão de obra)",
                    "example": 1
                  },
                  "unit_price": {
                    "type": "number",
                    "minimum": -1000000,
                    "maximum": 1000000,
                    "description": "Preço unitário de venda sem impostos, em euros",
                    "example": 65
                  },
                  "tax_rate": {
                    "type": [
                      "number",
                      "null"
                    ],
                    "minimum": 0,
                    "maximum": 30,
                    "description": "Alíquota de imposto; se omitida, a da oficina (IVA/IGIC/IPSI conforme a região)",
                    "example": 21
                  },
                  "discount_pct": {
                    "type": "number",
                    "minimum": 0,
                    "maximum": 100,
                    "description": "Desconto em %",
                    "example": 0
                  },
                  "line_type": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "enum": [
                      "labor",
                      "diagnosis",
                      "materials",
                      "parts",
                      "pieces",
                      "storage",
                      "other",
                      null
                    ],
                    "example": "labor"
                  },
                  "reference": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 100,
                    "description": "Código da peça",
                    "example": null
                  },
                  "group_title": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 200,
                    "description": "Título do grupo a que pertence",
                    "example": null
                  }
                },
                "required": [],
                "additionalProperties": false,
                "description": "Alterações parciais de um item"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Resposta bem-sucedida",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "epoch (s)"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "line",
                    "budget"
                  ],
                  "additionalProperties": false,
                  "description": "Item modificado e orçamento recalculado",
                  "properties": {
                    "line": {
                      "$ref": "#/components/schemas/BudgetLine"
                    },
                    "budget": {
                      "$ref": "#/components/schemas/BudgetDetail"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Erro — bad_request: A requisição não é válida.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Erro — missing_api_key: Falta a chave de API: envie no cabeçalho Authorization: Bearer pt_… ou X-Api-Key. | invalid_api_key: A chave de API não é válida para esta instância.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Erro — ip_not_allowed: O endereço IP de origem não está na lista permitida desta chave. | insufficient_scope: A chave de API não tem a permissão necessária para esta operação. | plan_scope_not_allowed: O plano de API da oficina não inclui esta permissão. Para usá-la é preciso mudar de plano. | test_key_forbidden: Uma chave de teste (pt_test_) não pode fazer operações com efeitos: use uma chave live.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Erro — not_found: Recurso não encontrado.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Erro — budget_locked: O orçamento está fechado e não aceita mais essa alteração.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "Erro — validation_error: O corpo da requisição não é válido: confira a lista «fields».",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ValidationError"
                }
              }
            }
          },
          "429": {
            "description": "Erro — rate_limited: Você excedeu o limite de requisições desta chave. Aguarde e tente de novo. | plan_quota_exceeded: A cota diária de requisições do plano de API da oficina acabou. Ela renova às 00:00 UTC; para mais volume, mude de plano.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Erro — internal_error: Erro interno.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Erro — instance_rate_limited: A instância está recebendo requisições demais pela API neste momento. Tente de novo em alguns segundos.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "delete": {
        "operationId": "deleteBudgetLine",
        "tags": [
          "Orçamentos"
        ],
        "summary": "Remover um item",
        "description": "Devolve o orçamento com os totais recalculados. O item fica no instantâneo anterior do histórico de versões.\n\nPermissão necessária: `budgets:write`.",
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ],
        "x-scope": "budgets:write",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Id do orçamento.",
            "schema": {
              "type": "integer",
              "example": 1234
            }
          },
          {
            "name": "lineId",
            "in": "path",
            "required": true,
            "description": "Id do item.",
            "schema": {
              "type": "integer",
              "example": 5501
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Resposta bem-sucedida",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "epoch (s)"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/BudgetDetail"
                }
              }
            }
          },
          "400": {
            "description": "Erro — bad_request: A requisição não é válida.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Erro — missing_api_key: Falta a chave de API: envie no cabeçalho Authorization: Bearer pt_… ou X-Api-Key. | invalid_api_key: A chave de API não é válida para esta instância.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Erro — ip_not_allowed: O endereço IP de origem não está na lista permitida desta chave. | insufficient_scope: A chave de API não tem a permissão necessária para esta operação. | plan_scope_not_allowed: O plano de API da oficina não inclui esta permissão. Para usá-la é preciso mudar de plano. | test_key_forbidden: Uma chave de teste (pt_test_) não pode fazer operações com efeitos: use uma chave live.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Erro — not_found: Recurso não encontrado.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Erro — budget_locked: O orçamento está fechado e não aceita mais essa alteração.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Erro — rate_limited: Você excedeu o limite de requisições desta chave. Aguarde e tente de novo. | plan_quota_exceeded: A cota diária de requisições do plano de API da oficina acabou. Ela renova às 00:00 UTC; para mais volume, mude de plano.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Erro — internal_error: Erro interno.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Erro — instance_rate_limited: A instância está recebendo requisições demais pela API neste momento. Tente de novo em alguns segundos.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/budgets/{id}/status": {
      "post": {
        "operationId": "changeBudgetStatus",
        "tags": [
          "Orçamentos"
        ],
        "summary": "Alterar o status",
        "description": "Aceita Pendiente/En cotización/Enviado (antes da aprovação), En curso (orçamento já aprovado), Finalizado (a partir de Aprobado, En curso ou En espera) e Cancelado. «Aprobado» responde 403 client_acceptance_required com a tracking_url: quem assina a aprovação é o cliente. Faturar, recusar ou desistir respondem 403 status_transition_forbidden; a partir de Facturado ou Cancelado, 409 budget_locked. «Enviado» exige também communications:send e sent_via: registra que o SEU sistema já enviou (não envia). Nenhuma mudança avisa o cliente, exceto Finalizado com notify_client=true e communications:send.\n\nPermissão necessária: `budgets:write`.",
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ],
        "x-scope": "budgets:write",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Id do orçamento.",
            "schema": {
              "type": "integer",
              "example": 1234
            }
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": true,
            "description": "Chave única por operação (UUID recomendado). Repeti-la com o mesmo corpo em 24 h devolve a resposta guardada com Idempotent-Replay: true; com outro corpo, 409 idempotency_conflict.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 255,
              "example": "5f0c7a52-3d1e-4b8a-9c61-2f7e1b0d4a93"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "status": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 40,
                    "description": "Pendiente, En cotización, Enviado, En curso, Finalizado ou Cancelado",
                    "example": "En curso"
                  },
                  "sent_via": {
                    "type": "string",
                    "enum": [
                      "email",
                      "sms",
                      "whatsapp"
                    ],
                    "description": "Só com status=Enviado: canal pelo qual o SEU sistema enviou o orçamento. Exige communications:send",
                    "example": "email"
                  },
                  "notify_client": {
                    "type": "boolean",
                    "description": "Com Finalizado: avisar o cliente de que o veículo está pronto (conforme os avisos configurados). Exige communications:send",
                    "example": false
                  }
                },
                "required": [
                  "status"
                ],
                "additionalProperties": false,
                "description": "Mudança de status"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Resposta bem-sucedida",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "epoch (s)"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/BudgetDetail"
                }
              }
            }
          },
          "400": {
            "description": "Erro — bad_request: A requisição não é válida. | idempotency_key_required: Falta o cabeçalho Idempotency-Key (obrigatório em todo POST; de 8 a 255 caracteres visíveis).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Erro — missing_api_key: Falta a chave de API: envie no cabeçalho Authorization: Bearer pt_… ou X-Api-Key. | invalid_api_key: A chave de API não é válida para esta instância.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Erro — ip_not_allowed: O endereço IP de origem não está na lista permitida desta chave. | insufficient_scope: A chave de API não tem a permissão necessária para esta operação. | plan_scope_not_allowed: O plano de API da oficina não inclui esta permissão. Para usá-la é preciso mudar de plano. | test_key_forbidden: Uma chave de teste (pt_test_) não pode fazer operações com efeitos: use uma chave live. | client_acceptance_required: A aprovação do orçamento precisa ser feita pelo cliente no link de acompanhamento assinado. | status_transition_forbidden: Essa mudança de status não está disponível pela API.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Erro — not_found: Recurso não encontrado.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Erro — invalid_status_transition: O orçamento não pode passar para esse status a partir do atual. | budget_locked: O orçamento está fechado e não aceita mais essa alteração. | idempotency_conflict: Essa Idempotency-Key já foi usada nas últimas 24 h com outra requisição. | idempotency_in_progress: Há outra requisição com a mesma Idempotency-Key em andamento. Tente de novo em alguns segundos.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "Erro — validation_error: O corpo da requisição não é válido: confira a lista «fields».",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ValidationError"
                }
              }
            }
          },
          "429": {
            "description": "Erro — rate_limited: Você excedeu o limite de requisições desta chave. Aguarde e tente de novo. | plan_quota_exceeded: A cota diária de requisições do plano de API da oficina acabou. Ela renova às 00:00 UTC; para mais volume, mude de plano.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Erro — internal_error: Erro interno.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Erro — instance_rate_limited: A instância está recebendo requisições demais pela API neste momento. Tente de novo em alguns segundos.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/budgets/{id}/documents": {
      "post": {
        "operationId": "uploadBudgetDocument",
        "tags": [
          "Orçamentos"
        ],
        "summary": "Anexar um documento",
        "description": "multipart/form-data com o campo «file» (JPEG, PNG, WebP ou PDF, verificado pelo conteúdo; máximo de 4 MB). Por padrão só a oficina vê; client_visible=true mostra no link de acompanhamento e mechanic_visible=true no app do mecânico. A impressão de idempotência inclui o arquivo.\n\nPermissão necessária: `budgets:write`.",
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ],
        "x-scope": "budgets:write",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Id do orçamento.",
            "schema": {
              "type": "integer",
              "example": 1234
            }
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": true,
            "description": "Chave única por operação (UUID recomendado). Repeti-la com o mesmo corpo em 24 h devolve a resposta guardada com Idempotent-Replay: true; com outro corpo, 409 idempotency_conflict.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 255,
              "example": "5f0c7a52-3d1e-4b8a-9c61-2f7e1b0d4a93"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "multipart/form-data": {
              "schema": {
                "type": "object",
                "required": [
                  "file"
                ],
                "additionalProperties": false,
                "properties": {
                  "file": {
                    "type": "string",
                    "format": "binary",
                    "description": "Fichero"
                  },
                  "client_visible": {
                    "type": "string",
                    "enum": [
                      "true",
                      "false"
                    ],
                    "example": "false"
                  },
                  "mechanic_visible": {
                    "type": "string",
                    "enum": [
                      "true",
                      "false"
                    ],
                    "example": "false"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Resposta bem-sucedida",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "epoch (s)"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/BudgetDocument"
                }
              }
            }
          },
          "400": {
            "description": "Erro — bad_request: A requisição não é válida. | idempotency_key_required: Falta o cabeçalho Idempotency-Key (obrigatório em todo POST; de 8 a 255 caracteres visíveis).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Erro — missing_api_key: Falta a chave de API: envie no cabeçalho Authorization: Bearer pt_… ou X-Api-Key. | invalid_api_key: A chave de API não é válida para esta instância.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Erro — ip_not_allowed: O endereço IP de origem não está na lista permitida desta chave. | insufficient_scope: A chave de API não tem a permissão necessária para esta operação. | plan_scope_not_allowed: O plano de API da oficina não inclui esta permissão. Para usá-la é preciso mudar de plano. | test_key_forbidden: Uma chave de teste (pt_test_) não pode fazer operações com efeitos: use uma chave live.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Erro — not_found: Recurso não encontrado.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Erro — idempotency_conflict: Essa Idempotency-Key já foi usada nas últimas 24 h com outra requisição. | idempotency_in_progress: Há outra requisição com a mesma Idempotency-Key em andamento. Tente de novo em alguns segundos.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "413": {
            "description": "Erro — payload_too_large: O arquivo excede o tamanho máximo (4 MB).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "415": {
            "description": "Erro — unsupported_media_type: Tipo de arquivo não aceito: só JPEG, PNG, WebP ou PDF.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "Erro — validation_error: O corpo da requisição não é válido: confira a lista «fields».",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ValidationError"
                }
              }
            }
          },
          "429": {
            "description": "Erro — rate_limited: Você excedeu o limite de requisições desta chave. Aguarde e tente de novo. | plan_quota_exceeded: A cota diária de requisições do plano de API da oficina acabou. Ela renova às 00:00 UTC; para mais volume, mude de plano.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Erro — internal_error: Erro interno.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Erro — instance_rate_limited: A instância está recebendo requisições demais pela API neste momento. Tente de novo em alguns segundos.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/invoices": {
      "get": {
        "operationId": "listInvoices",
        "tags": [
          "Faturas"
        ],
        "summary": "Listar faturas",
        "description": "Faturas emitidas e rascunhos, ordenados por id. Inclui o resumo de recebimentos.\n\nPermissão necessária: `invoices:read`.",
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ],
        "x-scope": "invoices:read",
        "parameters": [
          {
            "name": "from",
            "in": "query",
            "required": false,
            "description": "Data da fatura a partir de.",
            "schema": {
              "type": "string",
              "format": "date-time",
              "example": "2026-10-01"
            }
          },
          {
            "name": "to",
            "in": "query",
            "required": false,
            "description": "Data da fatura até.",
            "schema": {
              "type": "string",
              "format": "date-time",
              "example": "2026-10-31"
            }
          },
          {
            "name": "client_id",
            "in": "query",
            "required": false,
            "description": "Filtrar por cliente.",
            "schema": {
              "type": "integer",
              "example": 1204
            }
          },
          {
            "name": "status",
            "in": "query",
            "required": false,
            "description": "Status da fatura.",
            "schema": {
              "type": "string",
              "enum": [
                "draft",
                "issued",
                "cancelled"
              ]
            }
          },
          {
            "name": "series",
            "in": "query",
            "required": false,
            "description": "Série exata.",
            "schema": {
              "type": "string",
              "example": "F26"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Resultados por página (1–200).",
            "schema": {
              "type": "integer",
              "default": 50,
              "example": 50
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "required": false,
            "description": "Cursor opaco devolvido em next_cursor da página anterior.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Resposta bem-sucedida",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "epoch (s)"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data",
                    "next_cursor",
                    "has_more"
                  ],
                  "additionalProperties": false,
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Invoice"
                      }
                    },
                    "next_cursor": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "description": "Cursor opaco para pedir a próxima página; null se não houver mais",
                      "example": "aWQ6MTIzNA"
                    },
                    "has_more": {
                      "type": "boolean",
                      "description": "true se ainda houver resultados",
                      "example": true
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Erro — bad_request: A requisição não é válida.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Erro — missing_api_key: Falta a chave de API: envie no cabeçalho Authorization: Bearer pt_… ou X-Api-Key. | invalid_api_key: A chave de API não é válida para esta instância.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Erro — ip_not_allowed: O endereço IP de origem não está na lista permitida desta chave. | insufficient_scope: A chave de API não tem a permissão necessária para esta operação. | plan_scope_not_allowed: O plano de API da oficina não inclui esta permissão. Para usá-la é preciso mudar de plano.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Erro — rate_limited: Você excedeu o limite de requisições desta chave. Aguarde e tente de novo. | plan_quota_exceeded: A cota diária de requisições do plano de API da oficina acabou. Ela renova às 00:00 UTC; para mais volume, mude de plano.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Erro — internal_error: Erro interno.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Erro — instance_rate_limited: A instância está recebendo requisições demais pela API neste momento. Tente de novo em alguns segundos.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/invoices/{id}": {
      "get": {
        "operationId": "getInvoice",
        "tags": [
          "Faturas"
        ],
        "summary": "Detalhe de uma fatura",
        "description": "Permissão necessária: `invoices:read`.",
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ],
        "x-scope": "invoices:read",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Id da fatura.",
            "schema": {
              "type": "integer",
              "example": 1234
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Resposta bem-sucedida",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "epoch (s)"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/InvoiceDetail"
                }
              }
            }
          },
          "400": {
            "description": "Erro — bad_request: A requisição não é válida.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Erro — missing_api_key: Falta a chave de API: envie no cabeçalho Authorization: Bearer pt_… ou X-Api-Key. | invalid_api_key: A chave de API não é válida para esta instância.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Erro — ip_not_allowed: O endereço IP de origem não está na lista permitida desta chave. | insufficient_scope: A chave de API não tem a permissão necessária para esta operação. | plan_scope_not_allowed: O plano de API da oficina não inclui esta permissão. Para usá-la é preciso mudar de plano.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Erro — not_found: Recurso não encontrado.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Erro — rate_limited: Você excedeu o limite de requisições desta chave. Aguarde e tente de novo. | plan_quota_exceeded: A cota diária de requisições do plano de API da oficina acabou. Ela renova às 00:00 UTC; para mais volume, mude de plano.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Erro — internal_error: Erro interno.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Erro — instance_rate_limited: A instância está recebendo requisições demais pela API neste momento. Tente de novo em alguns segundos.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/invoices/{id}/pdf": {
      "get": {
        "operationId": "getInvoicePdf",
        "tags": [
          "Faturas"
        ],
        "summary": "PDF de uma fatura",
        "description": "O mesmo PDF que o programa gera (com QR Verifactu se a fatura estiver emitida). Resposta application/pdf.\n\nPermissão necessária: `invoices:read`.",
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ],
        "x-scope": "invoices:read",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Id da fatura.",
            "schema": {
              "type": "integer",
              "example": 1234
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Resposta bem-sucedida",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "epoch (s)"
              }
            },
            "content": {
              "application/pdf": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            }
          },
          "400": {
            "description": "Erro — bad_request: A requisição não é válida.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Erro — missing_api_key: Falta a chave de API: envie no cabeçalho Authorization: Bearer pt_… ou X-Api-Key. | invalid_api_key: A chave de API não é válida para esta instância.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Erro — ip_not_allowed: O endereço IP de origem não está na lista permitida desta chave. | insufficient_scope: A chave de API não tem a permissão necessária para esta operação. | plan_scope_not_allowed: O plano de API da oficina não inclui esta permissão. Para usá-la é preciso mudar de plano.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Erro — not_found: Recurso não encontrado.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Erro — rate_limited: Você excedeu o limite de requisições desta chave. Aguarde e tente de novo. | plan_quota_exceeded: A cota diária de requisições do plano de API da oficina acabou. Ela renova às 00:00 UTC; para mais volume, mude de plano.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Erro — internal_error: Erro interno.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Erro — instance_rate_limited: A instância está recebendo requisições demais pela API neste momento. Tente de novo em alguns segundos.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/appointments": {
      "get": {
        "operationId": "listAppointments",
        "tags": [
          "Agendamentos"
        ],
        "summary": "Agendamentos confirmados e propostas",
        "description": "Por padrão, os próximos 30 dias. status=confirmed são agendamentos fixados na agenda; status=proposed são propostas do cliente esperando a confirmação da oficina (bloqueiam o horário).\n\nPermissão necessária: `appointments:read`.",
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ],
        "x-scope": "appointments:read",
        "parameters": [
          {
            "name": "from",
            "in": "query",
            "required": false,
            "description": "Início do intervalo (padrão: agora).",
            "schema": {
              "type": "string",
              "format": "date-time",
              "example": "2026-10-01"
            }
          },
          {
            "name": "to",
            "in": "query",
            "required": false,
            "description": "Fim do intervalo (padrão: +30 dias, máximo 1 ano).",
            "schema": {
              "type": "string",
              "format": "date-time",
              "example": "2026-10-31"
            }
          },
          {
            "name": "status",
            "in": "query",
            "required": false,
            "description": "Só um tipo.",
            "schema": {
              "type": "string",
              "enum": [
                "confirmed",
                "proposed"
              ]
            }
          },
          {
            "name": "box_id",
            "in": "query",
            "required": false,
            "description": "Filtrar por elevador/box.",
            "schema": {
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Resposta bem-sucedida",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "epoch (s)"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data",
                    "from",
                    "to"
                  ],
                  "additionalProperties": false,
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Appointment"
                      }
                    },
                    "from": {
                      "type": "string",
                      "format": "date-time",
                      "example": "2026-10-06T00:00:00.000Z"
                    },
                    "to": {
                      "type": "string",
                      "format": "date-time",
                      "example": "2026-11-05T00:00:00.000Z"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Erro — bad_request: A requisição não é válida.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Erro — missing_api_key: Falta a chave de API: envie no cabeçalho Authorization: Bearer pt_… ou X-Api-Key. | invalid_api_key: A chave de API não é válida para esta instância.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Erro — ip_not_allowed: O endereço IP de origem não está na lista permitida desta chave. | insufficient_scope: A chave de API não tem a permissão necessária para esta operação. | plan_scope_not_allowed: O plano de API da oficina não inclui esta permissão. Para usá-la é preciso mudar de plano.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Erro — rate_limited: Você excedeu o limite de requisições desta chave. Aguarde e tente de novo. | plan_quota_exceeded: A cota diária de requisições do plano de API da oficina acabou. Ela renova às 00:00 UTC; para mais volume, mude de plano.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Erro — internal_error: Erro interno.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Erro — instance_rate_limited: A instância está recebendo requisições demais pela API neste momento. Tente de novo em alguns segundos.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "post": {
        "operationId": "createAppointment",
        "tags": [
          "Agendamentos"
        ],
        "summary": "Propor ou confirmar um agendamento",
        "description": "Segue o modo de agendamento da oficina (booking_mode em GET /workshop). Em «propose» é criada uma proposta (status=proposed) que bloqueia o horário até a oficina confirmar; pedir mode=book responde 403 booking_mode_propose_only. Em «book» o agendamento fica firme (status=confirmed), a menos que você peça mode=propose. O horário é validado com a mesma lógica de /appointments/availability; se não estiver livre, 409 slot_unavailable com até 3 alternativas em error.alternatives. O cliente não é avisado, exceto com notify_client=true e communications:send.\n\nPermissão necessária: `appointments:write`.",
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ],
        "x-scope": "appointments:write",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": true,
            "description": "Chave única por operação (UUID recomendado). Repeti-la com o mesmo corpo em 24 h devolve a resposta guardada com Idempotent-Replay: true; com outro corpo, 409 idempotency_conflict.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 255,
              "example": "5f0c7a52-3d1e-4b8a-9c61-2f7e1b0d4a93"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "budget_id": {
                    "type": "integer",
                    "minimum": 1,
                    "description": "Orçamento ao qual o agendamento pertence",
                    "example": 1234
                  },
                  "start": {
                    "type": "string",
                    "format": "date-time",
                    "description": "Início com fuso horário (ISO 8601)",
                    "example": "2026-10-14T09:00:00+02:00"
                  },
                  "box_id": {
                    "type": "integer",
                    "minimum": 1,
                    "description": "Elevador/box; se omitido, o primeiro livre",
                    "example": 2
                  },
                  "duration_minutes": {
                    "type": "integer",
                    "minimum": 15,
                    "maximum": 720,
                    "description": "Duração; padrão: a mínima da agenda",
                    "example": 60
                  },
                  "mode": {
                    "type": "string",
                    "enum": [
                      "propose",
                      "book"
                    ],
                    "description": "propose: proposta que a oficina confirma. book: agendamento firme (só se a oficina trabalha no modo «reservar»). Padrão: o modo da oficina",
                    "example": "propose"
                  },
                  "notify_client": {
                    "type": "boolean",
                    "description": "Enviar a confirmação do agendamento ao cliente (só agendamentos firmes). Exige communications:send",
                    "example": false
                  }
                },
                "required": [
                  "budget_id",
                  "start"
                ],
                "additionalProperties": false,
                "description": "Agendamento ou proposta de agendamento"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Resposta bem-sucedida",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "epoch (s)"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Appointment"
                }
              }
            }
          },
          "400": {
            "description": "Erro — bad_request: A requisição não é válida. | idempotency_key_required: Falta o cabeçalho Idempotency-Key (obrigatório em todo POST; de 8 a 255 caracteres visíveis).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Erro — missing_api_key: Falta a chave de API: envie no cabeçalho Authorization: Bearer pt_… ou X-Api-Key. | invalid_api_key: A chave de API não é válida para esta instância.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Erro — ip_not_allowed: O endereço IP de origem não está na lista permitida desta chave. | insufficient_scope: A chave de API não tem a permissão necessária para esta operação. | plan_scope_not_allowed: O plano de API da oficina não inclui esta permissão. Para usá-la é preciso mudar de plano. | test_key_forbidden: Uma chave de teste (pt_test_) não pode fazer operações com efeitos: use uma chave live. | booking_mode_propose_only: A oficina trabalha no modo «propor agendamento»: só é possível criar propostas que a oficina confirma.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Erro — slot_unavailable: Esse horário não está disponível. | budget_locked: O orçamento está fechado e não aceita mais essa alteração. | idempotency_conflict: Essa Idempotency-Key já foi usada nas últimas 24 h com outra requisição. | idempotency_in_progress: Há outra requisição com a mesma Idempotency-Key em andamento. Tente de novo em alguns segundos.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "Erro — validation_error: O corpo da requisição não é válido: confira a lista «fields».",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ValidationError"
                }
              }
            }
          },
          "429": {
            "description": "Erro — rate_limited: Você excedeu o limite de requisições desta chave. Aguarde e tente de novo. | plan_quota_exceeded: A cota diária de requisições do plano de API da oficina acabou. Ela renova às 00:00 UTC; para mais volume, mude de plano.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Erro — internal_error: Erro interno.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Erro — instance_rate_limited: A instância está recebendo requisições demais pela API neste momento. Tente de novo em alguns segundos.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/appointments/availability": {
      "get": {
        "operationId": "getAvailability",
        "tags": [
          "Agendamentos"
        ],
        "summary": "Horários livres",
        "description": "Mesma lógica da agenda e do site de acompanhamento: horário da oficina e de cada box, almoço, feriados nacionais, regionais e locais, agendamentos abertos e propostas pendentes (que bloqueiam o horário). Inícios a cada 30 min e com pelo menos 1 h de antecedência. Padrão: os próximos 7 dias (máximo 31). Inclui o booking_mode da oficina.\n\nPermissão necessária: `appointments:read`.",
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ],
        "x-scope": "appointments:read",
        "parameters": [
          {
            "name": "from",
            "in": "query",
            "required": false,
            "description": "De (padrão: agora).",
            "schema": {
              "type": "string",
              "format": "date-time",
              "example": "2026-10-01"
            }
          },
          {
            "name": "to",
            "in": "query",
            "required": false,
            "description": "Até (padrão: +7 dias; máximo 31 dias).",
            "schema": {
              "type": "string",
              "format": "date-time",
              "example": "2026-10-31"
            }
          },
          {
            "name": "duration_minutes",
            "in": "query",
            "required": false,
            "description": "Duração do agendamento (15–720). Padrão: a mínima da agenda.",
            "schema": {
              "type": "integer",
              "example": 60
            }
          },
          {
            "name": "box_id",
            "in": "query",
            "required": false,
            "description": "Só esse box.",
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Máximo de horários (1–500).",
            "schema": {
              "type": "integer",
              "default": 100
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Resposta bem-sucedida",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "epoch (s)"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AppointmentAvailability"
                }
              }
            }
          },
          "400": {
            "description": "Erro — bad_request: A requisição não é válida.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Erro — missing_api_key: Falta a chave de API: envie no cabeçalho Authorization: Bearer pt_… ou X-Api-Key. | invalid_api_key: A chave de API não é válida para esta instância.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Erro — ip_not_allowed: O endereço IP de origem não está na lista permitida desta chave. | insufficient_scope: A chave de API não tem a permissão necessária para esta operação. | plan_scope_not_allowed: O plano de API da oficina não inclui esta permissão. Para usá-la é preciso mudar de plano.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Erro — rate_limited: Você excedeu o limite de requisições desta chave. Aguarde e tente de novo. | plan_quota_exceeded: A cota diária de requisições do plano de API da oficina acabou. Ela renova às 00:00 UTC; para mais volume, mude de plano.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Erro — internal_error: Erro interno.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Erro — instance_rate_limited: A instância está recebendo requisições demais pela API neste momento. Tente de novo em alguns segundos.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/appointments/{id}": {
      "delete": {
        "operationId": "cancelAppointment",
        "tags": [
          "Agendamentos"
        ],
        "summary": "Cancelar um agendamento ou retirar uma proposta",
        "description": "b<orçamento>: cancela o agendamento confirmado (igual a «Cancelar agendamento» no cadastro). p<proposta>: retira a proposta pendente; com notify_client=true e communications:send o cliente é convidado a escolher outro horário. Emite appointment.cancelled.\n\nPermissão necessária: `appointments:write`.",
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ],
        "x-scope": "appointments:write",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Id do agendamento (b1234 ou p88).",
            "schema": {
              "type": "string",
              "example": "b1234"
            }
          },
          {
            "name": "notify_client",
            "in": "query",
            "required": false,
            "description": "Só propostas: avisar o cliente.",
            "schema": {
              "type": "boolean",
              "default": "false"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Resposta bem-sucedida",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "epoch (s)"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AppointmentCancelled"
                }
              }
            }
          },
          "400": {
            "description": "Erro — bad_request: A requisição não é válida.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Erro — missing_api_key: Falta a chave de API: envie no cabeçalho Authorization: Bearer pt_… ou X-Api-Key. | invalid_api_key: A chave de API não é válida para esta instância.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Erro — ip_not_allowed: O endereço IP de origem não está na lista permitida desta chave. | insufficient_scope: A chave de API não tem a permissão necessária para esta operação. | plan_scope_not_allowed: O plano de API da oficina não inclui esta permissão. Para usá-la é preciso mudar de plano. | test_key_forbidden: Uma chave de teste (pt_test_) não pode fazer operações com efeitos: use uma chave live.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Erro — not_found: Recurso não encontrado.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Erro — rate_limited: Você excedeu o limite de requisições desta chave. Aguarde e tente de novo. | plan_quota_exceeded: A cota diária de requisições do plano de API da oficina acabou. Ela renova às 00:00 UTC; para mais volume, mude de plano.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Erro — internal_error: Erro interno.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Erro — instance_rate_limited: A instância está recebendo requisições demais pela API neste momento. Tente de novo em alguns segundos.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/catalog/services": {
      "get": {
        "operationId": "listServices",
        "tags": [
          "Catálogo"
        ],
        "summary": "Categorias e subcategorias de serviço",
        "description": "Permissão necessária: `catalog:read`.",
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ],
        "x-scope": "catalog:read",
        "responses": {
          "200": {
            "description": "Resposta bem-sucedida",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "epoch (s)"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data"
                  ],
                  "additionalProperties": false,
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/ServiceCategory"
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Erro — bad_request: A requisição não é válida.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Erro — missing_api_key: Falta a chave de API: envie no cabeçalho Authorization: Bearer pt_… ou X-Api-Key. | invalid_api_key: A chave de API não é válida para esta instância.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Erro — ip_not_allowed: O endereço IP de origem não está na lista permitida desta chave. | insufficient_scope: A chave de API não tem a permissão necessária para esta operação. | plan_scope_not_allowed: O plano de API da oficina não inclui esta permissão. Para usá-la é preciso mudar de plano.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Erro — rate_limited: Você excedeu o limite de requisições desta chave. Aguarde e tente de novo. | plan_quota_exceeded: A cota diária de requisições do plano de API da oficina acabou. Ela renova às 00:00 UTC; para mais volume, mude de plano.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Erro — internal_error: Erro interno.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Erro — instance_rate_limited: A instância está recebendo requisições demais pela API neste momento. Tente de novo em alguns segundos.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/catalog/rates": {
      "get": {
        "operationId": "listRates",
        "tags": [
          "Catálogo"
        ],
        "summary": "Tarifas de mão de obra",
        "description": "Só o preço de venda por hora; o custo nunca sai pela API.\n\nPermissão necessária: `catalog:read`.",
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ],
        "x-scope": "catalog:read",
        "responses": {
          "200": {
            "description": "Resposta bem-sucedida",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "epoch (s)"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data"
                  ],
                  "additionalProperties": false,
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/LaborRate"
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Erro — bad_request: A requisição não é válida.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Erro — missing_api_key: Falta a chave de API: envie no cabeçalho Authorization: Bearer pt_… ou X-Api-Key. | invalid_api_key: A chave de API não é válida para esta instância.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Erro — ip_not_allowed: O endereço IP de origem não está na lista permitida desta chave. | insufficient_scope: A chave de API não tem a permissão necessária para esta operação. | plan_scope_not_allowed: O plano de API da oficina não inclui esta permissão. Para usá-la é preciso mudar de plano.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Erro — rate_limited: Você excedeu o limite de requisições desta chave. Aguarde e tente de novo. | plan_quota_exceeded: A cota diária de requisições do plano de API da oficina acabou. Ela renova às 00:00 UTC; para mais volume, mude de plano.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Erro — internal_error: Erro interno.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Erro — instance_rate_limited: A instância está recebendo requisições demais pela API neste momento. Tente de novo em alguns segundos.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/communications": {
      "get": {
        "operationId": "listCommunications",
        "tags": [
          "Comunicações"
        ],
        "summary": "Registro de comunicações",
        "description": "Últimas comunicações (e-mail, SMS, WhatsApp, ligações, push) com o conteúdo completo, da mais recente para a mais antiga.\n\nPermissão necessária: `communications:read`.",
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ],
        "x-scope": "communications:read",
        "parameters": [
          {
            "name": "channel",
            "in": "query",
            "required": false,
            "description": "Canal.",
            "schema": {
              "type": "string",
              "enum": [
                "all",
                "email",
                "sms",
                "whatsapp",
                "call",
                "push"
              ],
              "default": "all"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Máximo de resultados (1–200).",
            "schema": {
              "type": "integer",
              "default": 50
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Resposta bem-sucedida",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "epoch (s)"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data",
                    "next_cursor",
                    "has_more"
                  ],
                  "additionalProperties": false,
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Communication"
                      }
                    },
                    "next_cursor": {
                      "type": "null"
                    },
                    "has_more": {
                      "type": "boolean",
                      "example": false
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Erro — bad_request: A requisição não é válida.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Erro — missing_api_key: Falta a chave de API: envie no cabeçalho Authorization: Bearer pt_… ou X-Api-Key. | invalid_api_key: A chave de API não é válida para esta instância.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Erro — ip_not_allowed: O endereço IP de origem não está na lista permitida desta chave. | insufficient_scope: A chave de API não tem a permissão necessária para esta operação. | plan_scope_not_allowed: O plano de API da oficina não inclui esta permissão. Para usá-la é preciso mudar de plano.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Erro — rate_limited: Você excedeu o limite de requisições desta chave. Aguarde e tente de novo. | plan_quota_exceeded: A cota diária de requisições do plano de API da oficina acabou. Ela renova às 00:00 UTC; para mais volume, mude de plano.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Erro — internal_error: Erro interno.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Erro — instance_rate_limited: A instância está recebendo requisições demais pela API neste momento. Tente de novo em alguns segundos.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/logs": {
      "get": {
        "operationId": "listLogs",
        "tags": [
          "Comunicações"
        ],
        "summary": "Registro de comunicações (alias antigo)",
        "description": "Mesma consulta de /communications, mas devolve { items }. Mantido por compatibilidade; use /communications.\n\nPermissão necessária: `communications:read`.",
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ],
        "x-scope": "communications:read",
        "parameters": [
          {
            "name": "channel",
            "in": "query",
            "required": false,
            "description": "Canal.",
            "schema": {
              "type": "string",
              "enum": [
                "all",
                "email",
                "sms",
                "whatsapp",
                "call",
                "push"
              ],
              "default": "all"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Máximo de resultados (1–200).",
            "schema": {
              "type": "integer",
              "default": 50
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Resposta bem-sucedida",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "epoch (s)"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "items"
                  ],
                  "additionalProperties": false,
                  "properties": {
                    "items": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Communication"
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Erro — bad_request: A requisição não é válida.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Erro — missing_api_key: Falta a chave de API: envie no cabeçalho Authorization: Bearer pt_… ou X-Api-Key. | invalid_api_key: A chave de API não é válida para esta instância.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Erro — ip_not_allowed: O endereço IP de origem não está na lista permitida desta chave. | insufficient_scope: A chave de API não tem a permissão necessária para esta operação. | plan_scope_not_allowed: O plano de API da oficina não inclui esta permissão. Para usá-la é preciso mudar de plano.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Erro — rate_limited: Você excedeu o limite de requisições desta chave. Aguarde e tente de novo. | plan_quota_exceeded: A cota diária de requisições do plano de API da oficina acabou. Ela renova às 00:00 UTC; para mais volume, mude de plano.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Erro — internal_error: Erro interno.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Erro — instance_rate_limited: A instância está recebendo requisições demais pela API neste momento. Tente de novo em alguns segundos.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/email": {
      "post": {
        "operationId": "sendEmail",
        "tags": [
          "Comunicações"
        ],
        "summary": "Enviar um email transacional",
        "description": "Sai pela conta de e-mail configurada na oficina e fica no registro de Comunicação. Se voltar, o e-mail do cliente é marcado como inválido.\n\nPermissão necessária: `communications:send`.",
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ],
        "x-scope": "communications:send",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "to",
                  "subject"
                ],
                "properties": {
                  "to": {
                    "type": "string",
                    "format": "email",
                    "example": "cliente@ejemplo.com"
                  },
                  "subject": {
                    "type": "string",
                    "example": "Seu veículo está pronto"
                  },
                  "text": {
                    "type": "string",
                    "example": "Você já pode retirá-lo."
                  },
                  "html": {
                    "type": "string",
                    "example": "<p>Você já pode retirá-lo.</p>"
                  },
                  "fromName": {
                    "type": "string",
                    "example": "Taller"
                  },
                  "replyTo": {
                    "type": "string",
                    "format": "email",
                    "example": "taller@ejemplo.com"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Resposta bem-sucedida",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "epoch (s)"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true,
                  "example": {
                    "ok": true
                  }
                }
              }
            }
          },
          "400": {
            "description": "Erro — bad_request: A requisição não é válida.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Erro — missing_api_key: Falta a chave de API: envie no cabeçalho Authorization: Bearer pt_… ou X-Api-Key. | invalid_api_key: A chave de API não é válida para esta instância.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Erro — ip_not_allowed: O endereço IP de origem não está na lista permitida desta chave. | insufficient_scope: A chave de API não tem a permissão necessária para esta operação. | plan_scope_not_allowed: O plano de API da oficina não inclui esta permissão. Para usá-la é preciso mudar de plano. | test_key_forbidden: Uma chave de teste (pt_test_) não pode fazer operações com efeitos: use uma chave live.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Erro — rate_limited: Você excedeu o limite de requisições desta chave. Aguarde e tente de novo. | plan_quota_exceeded: A cota diária de requisições do plano de API da oficina acabou. Ela renova às 00:00 UTC; para mais volume, mude de plano.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Erro — internal_error: Erro interno.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Erro — instance_rate_limited: A instância está recebendo requisições demais pela API neste momento. Tente de novo em alguns segundos.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/sms": {
      "post": {
        "operationId": "sendSms",
        "tags": [
          "Comunicações"
        ],
        "summary": "Enviar um SMS transacional",
        "description": "Sai pelo serviço de SMS configurado na oficina e fica no registro de Comunicação, onde o status de entrega é atualizado.\n\nPermissão necessária: `communications:send`.",
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ],
        "x-scope": "communications:send",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "to",
                  "body"
                ],
                "properties": {
                  "to": {
                    "type": "string",
                    "example": "+34600111222"
                  },
                  "body": {
                    "type": "string",
                    "example": "Seu veículo está pronto para retirada."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Resposta bem-sucedida",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "epoch (s)"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true,
                  "example": {
                    "ok": true
                  }
                }
              }
            }
          },
          "400": {
            "description": "Erro — bad_request: A requisição não é válida.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Erro — missing_api_key: Falta a chave de API: envie no cabeçalho Authorization: Bearer pt_… ou X-Api-Key. | invalid_api_key: A chave de API não é válida para esta instância.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Erro — ip_not_allowed: O endereço IP de origem não está na lista permitida desta chave. | insufficient_scope: A chave de API não tem a permissão necessária para esta operação. | plan_scope_not_allowed: O plano de API da oficina não inclui esta permissão. Para usá-la é preciso mudar de plano. | test_key_forbidden: Uma chave de teste (pt_test_) não pode fazer operações com efeitos: use uma chave live.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Erro — rate_limited: Você excedeu o limite de requisições desta chave. Aguarde e tente de novo. | plan_quota_exceeded: A cota diária de requisições do plano de API da oficina acabou. Ela renova às 00:00 UTC; para mais volume, mude de plano.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Erro — internal_error: Erro interno.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Erro — instance_rate_limited: A instância está recebendo requisições demais pela API neste momento. Tente de novo em alguns segundos.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/webhooks": {
      "get": {
        "operationId": "listWebhooks",
        "tags": [
          "Webhooks"
        ],
        "summary": "Listar os webhooks da chave",
        "description": "Inclui o catálogo de eventos disponíveis. Nunca devolve os segredos.\n\nPermissão necessária: `webhooks:manage`.",
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ],
        "x-scope": "webhooks:manage",
        "responses": {
          "200": {
            "description": "Resposta bem-sucedida",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "epoch (s)"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "items",
                    "events"
                  ],
                  "properties": {
                    "items": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Webhook"
                      }
                    },
                    "events": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "event": {
                            "type": "string",
                            "example": "lead.accepted"
                          },
                          "label": {
                            "type": "string",
                            "example": "Orçamento aprovado"
                          },
                          "description": {
                            "type": "string"
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Erro — bad_request: A requisição não é válida.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Erro — missing_api_key: Falta a chave de API: envie no cabeçalho Authorization: Bearer pt_… ou X-Api-Key. | invalid_api_key: A chave de API não é válida para esta instância.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Erro — ip_not_allowed: O endereço IP de origem não está na lista permitida desta chave. | insufficient_scope: A chave de API não tem a permissão necessária para esta operação. | plan_scope_not_allowed: O plano de API da oficina não inclui esta permissão. Para usá-la é preciso mudar de plano. | test_key_forbidden: Uma chave de teste (pt_test_) não pode fazer operações com efeitos: use uma chave live.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Erro — rate_limited: Você excedeu o limite de requisições desta chave. Aguarde e tente de novo. | plan_quota_exceeded: A cota diária de requisições do plano de API da oficina acabou. Ela renova às 00:00 UTC; para mais volume, mude de plano.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Erro — internal_error: Erro interno.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Erro — instance_rate_limited: A instância está recebendo requisições demais pela API neste momento. Tente de novo em alguns segundos.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "post": {
        "operationId": "createWebhook",
        "tags": [
          "Webhooks"
        ],
        "summary": "Criar um webhook",
        "description": "O segredo de assinatura (whsec_…) só vem nesta resposta.\n\nPermissão necessária: `webhooks:manage`.",
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ],
        "x-scope": "webhooks:manage",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "url",
                  "events"
                ],
                "properties": {
                  "url": {
                    "type": "string",
                    "format": "uri",
                    "example": "https://tu-sistema.com/webhooks/taller"
                  },
                  "events": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "example": "lead.accepted"
                    }
                  },
                  "description": {
                    "type": "string",
                    "example": "CRM"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Resposta bem-sucedida",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "epoch (s)"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "item",
                    "secret"
                  ],
                  "properties": {
                    "item": {
                      "$ref": "#/components/schemas/Webhook"
                    },
                    "secret": {
                      "type": "string",
                      "example": "whsec_…"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Erro — bad_request: A requisição não é válida.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Erro — missing_api_key: Falta a chave de API: envie no cabeçalho Authorization: Bearer pt_… ou X-Api-Key. | invalid_api_key: A chave de API não é válida para esta instância.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Erro — ip_not_allowed: O endereço IP de origem não está na lista permitida desta chave. | insufficient_scope: A chave de API não tem a permissão necessária para esta operação. | plan_scope_not_allowed: O plano de API da oficina não inclui esta permissão. Para usá-la é preciso mudar de plano. | test_key_forbidden: Uma chave de teste (pt_test_) não pode fazer operações com efeitos: use uma chave live.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Erro — rate_limited: Você excedeu o limite de requisições desta chave. Aguarde e tente de novo. | plan_quota_exceeded: A cota diária de requisições do plano de API da oficina acabou. Ela renova às 00:00 UTC; para mais volume, mude de plano.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Erro — internal_error: Erro interno.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Erro — instance_rate_limited: A instância está recebendo requisições demais pela API neste momento. Tente de novo em alguns segundos.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/webhooks/{id}": {
      "get": {
        "operationId": "getWebhook",
        "tags": [
          "Webhooks"
        ],
        "summary": "Detalhe de um webhook e últimas entregas",
        "description": "Permissão necessária: `webhooks:manage`.",
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ],
        "x-scope": "webhooks:manage",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Id do webhook.",
            "schema": {
              "type": "integer",
              "example": 1234
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Resposta bem-sucedida",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "epoch (s)"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true,
                  "properties": {
                    "item": {
                      "$ref": "#/components/schemas/Webhook"
                    },
                    "deliveries": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "additionalProperties": true
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Erro — bad_request: A requisição não é válida.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Erro — missing_api_key: Falta a chave de API: envie no cabeçalho Authorization: Bearer pt_… ou X-Api-Key. | invalid_api_key: A chave de API não é válida para esta instância.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Erro — ip_not_allowed: O endereço IP de origem não está na lista permitida desta chave. | insufficient_scope: A chave de API não tem a permissão necessária para esta operação. | plan_scope_not_allowed: O plano de API da oficina não inclui esta permissão. Para usá-la é preciso mudar de plano. | test_key_forbidden: Uma chave de teste (pt_test_) não pode fazer operações com efeitos: use uma chave live.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Erro — not_found: Recurso não encontrado.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Erro — rate_limited: Você excedeu o limite de requisições desta chave. Aguarde e tente de novo. | plan_quota_exceeded: A cota diária de requisições do plano de API da oficina acabou. Ela renova às 00:00 UTC; para mais volume, mude de plano.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Erro — internal_error: Erro interno.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Erro — instance_rate_limited: A instância está recebendo requisições demais pela API neste momento. Tente de novo em alguns segundos.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "patch": {
        "operationId": "updateWebhook",
        "tags": [
          "Webhooks"
        ],
        "summary": "Modificar um webhook",
        "description": "Permissão necessária: `webhooks:manage`.",
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ],
        "x-scope": "webhooks:manage",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Id do webhook.",
            "schema": {
              "type": "integer",
              "example": 1234
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "url": {
                    "type": "string",
                    "format": "uri"
                  },
                  "events": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    }
                  },
                  "description": {
                    "type": "string"
                  },
                  "active": {
                    "type": "boolean"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Resposta bem-sucedida",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "epoch (s)"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "item": {
                      "$ref": "#/components/schemas/Webhook"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Erro — bad_request: A requisição não é válida.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Erro — missing_api_key: Falta a chave de API: envie no cabeçalho Authorization: Bearer pt_… ou X-Api-Key. | invalid_api_key: A chave de API não é válida para esta instância.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Erro — ip_not_allowed: O endereço IP de origem não está na lista permitida desta chave. | insufficient_scope: A chave de API não tem a permissão necessária para esta operação. | plan_scope_not_allowed: O plano de API da oficina não inclui esta permissão. Para usá-la é preciso mudar de plano. | test_key_forbidden: Uma chave de teste (pt_test_) não pode fazer operações com efeitos: use uma chave live.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Erro — not_found: Recurso não encontrado.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Erro — rate_limited: Você excedeu o limite de requisições desta chave. Aguarde e tente de novo. | plan_quota_exceeded: A cota diária de requisições do plano de API da oficina acabou. Ela renova às 00:00 UTC; para mais volume, mude de plano.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Erro — internal_error: Erro interno.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Erro — instance_rate_limited: A instância está recebendo requisições demais pela API neste momento. Tente de novo em alguns segundos.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "delete": {
        "operationId": "deleteWebhook",
        "tags": [
          "Webhooks"
        ],
        "summary": "Excluir um webhook",
        "description": "Permissão necessária: `webhooks:manage`.",
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ],
        "x-scope": "webhooks:manage",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Id do webhook.",
            "schema": {
              "type": "integer",
              "example": 1234
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Resposta bem-sucedida",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "epoch (s)"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ok": {
                      "type": "boolean",
                      "example": true
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Erro — bad_request: A requisição não é válida.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Erro — missing_api_key: Falta a chave de API: envie no cabeçalho Authorization: Bearer pt_… ou X-Api-Key. | invalid_api_key: A chave de API não é válida para esta instância.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Erro — ip_not_allowed: O endereço IP de origem não está na lista permitida desta chave. | insufficient_scope: A chave de API não tem a permissão necessária para esta operação. | plan_scope_not_allowed: O plano de API da oficina não inclui esta permissão. Para usá-la é preciso mudar de plano. | test_key_forbidden: Uma chave de teste (pt_test_) não pode fazer operações com efeitos: use uma chave live.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Erro — not_found: Recurso não encontrado.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Erro — rate_limited: Você excedeu o limite de requisições desta chave. Aguarde e tente de novo. | plan_quota_exceeded: A cota diária de requisições do plano de API da oficina acabou. Ela renova às 00:00 UTC; para mais volume, mude de plano.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Erro — internal_error: Erro interno.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Erro — instance_rate_limited: A instância está recebendo requisições demais pela API neste momento. Tente de novo em alguns segundos.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/webhooks/{id}/test": {
      "post": {
        "operationId": "testWebhook",
        "tags": [
          "Webhooks"
        ],
        "summary": "Enviar uma entrega de teste (test.ping) ao webhook",
        "description": "Permissão necessária: `webhooks:manage`.",
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ],
        "x-scope": "webhooks:manage",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Id do webhook.",
            "schema": {
              "type": "integer",
              "example": 1234
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Resposta bem-sucedida",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "epoch (s)"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true,
                  "example": {
                    "ok": true,
                    "status": 200
                  }
                }
              }
            }
          },
          "400": {
            "description": "Erro — bad_request: A requisição não é válida.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Erro — missing_api_key: Falta a chave de API: envie no cabeçalho Authorization: Bearer pt_… ou X-Api-Key. | invalid_api_key: A chave de API não é válida para esta instância.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Erro — ip_not_allowed: O endereço IP de origem não está na lista permitida desta chave. | insufficient_scope: A chave de API não tem a permissão necessária para esta operação. | plan_scope_not_allowed: O plano de API da oficina não inclui esta permissão. Para usá-la é preciso mudar de plano. | test_key_forbidden: Uma chave de teste (pt_test_) não pode fazer operações com efeitos: use uma chave live.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Erro — not_found: Recurso não encontrado.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Erro — rate_limited: Você excedeu o limite de requisições desta chave. Aguarde e tente de novo. | plan_quota_exceeded: A cota diária de requisições do plano de API da oficina acabou. Ela renova às 00:00 UTC; para mais volume, mude de plano.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Erro — internal_error: Erro interno.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Erro — instance_rate_limited: A instância está recebendo requisições demais pela API neste momento. Tente de novo em alguns segundos.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    }
  },
  "webhooks": {
    "lead.created": {
      "post": {
        "summary": "Orçamento criado",
        "description": "Um novo orçamento foi criado (pelo programa, pelo site, por e-mail ou pelo assistente).\n\nEntrega POST assinada com X-PT-Signature (HMAC-SHA256 de \"<t>.<corpo>\"). Responda 2xx em menos de 5 s.",
        "tags": [
          "Orçamentos"
        ],
        "parameters": [
          {
            "name": "X-PT-Event",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string",
              "enum": [
                "lead.created"
              ]
            }
          },
          {
            "name": "X-PT-Delivery-Id",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "X-PT-Signature",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string",
              "example": "t=1760000000,v1=5f1c…e9a2"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/WebhookEnvelope"
              }
            }
          }
        },
        "responses": {
          "2xx": {
            "description": "Recebido"
          }
        }
      }
    },
    "lead.sent": {
      "post": {
        "summary": "Orçamento enviado",
        "description": "O orçamento foi enviado ao cliente.\n\nEntrega POST assinada com X-PT-Signature (HMAC-SHA256 de \"<t>.<corpo>\"). Responda 2xx em menos de 5 s.",
        "tags": [
          "Orçamentos"
        ],
        "parameters": [
          {
            "name": "X-PT-Event",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string",
              "enum": [
                "lead.sent"
              ]
            }
          },
          {
            "name": "X-PT-Delivery-Id",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "X-PT-Signature",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string",
              "example": "t=1760000000,v1=5f1c…e9a2"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/WebhookEnvelope"
              }
            }
          }
        },
        "responses": {
          "2xx": {
            "description": "Recebido"
          }
        }
      }
    },
    "lead.accepted": {
      "post": {
        "summary": "Orçamento aprovado",
        "description": "O cliente ou a oficina aprovou o orçamento.\n\nEntrega POST assinada com X-PT-Signature (HMAC-SHA256 de \"<t>.<corpo>\"). Responda 2xx em menos de 5 s.",
        "tags": [
          "Orçamentos"
        ],
        "parameters": [
          {
            "name": "X-PT-Event",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string",
              "enum": [
                "lead.accepted"
              ]
            }
          },
          {
            "name": "X-PT-Delivery-Id",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "X-PT-Signature",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string",
              "example": "t=1760000000,v1=5f1c…e9a2"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/WebhookEnvelope"
              }
            }
          }
        },
        "responses": {
          "2xx": {
            "description": "Recebido"
          }
        }
      }
    },
    "lead.rejected": {
      "post": {
        "summary": "Orçamento recusado",
        "description": "O orçamento foi recusado, com o motivo, se houver.\n\nEntrega POST assinada com X-PT-Signature (HMAC-SHA256 de \"<t>.<corpo>\"). Responda 2xx em menos de 5 s.",
        "tags": [
          "Orçamentos"
        ],
        "parameters": [
          {
            "name": "X-PT-Event",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string",
              "enum": [
                "lead.rejected"
              ]
            }
          },
          {
            "name": "X-PT-Delivery-Id",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "X-PT-Signature",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string",
              "example": "t=1760000000,v1=5f1c…e9a2"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/WebhookEnvelope"
              }
            }
          }
        },
        "responses": {
          "2xx": {
            "description": "Recebido"
          }
        }
      }
    },
    "lead.status_changed": {
      "post": {
        "summary": "Mudança de status",
        "description": "Qualquer mudança de status do orçamento (inclui as anteriores).\n\nEntrega POST assinada com X-PT-Signature (HMAC-SHA256 de \"<t>.<corpo>\"). Responda 2xx em menos de 5 s.",
        "tags": [
          "Orçamentos"
        ],
        "parameters": [
          {
            "name": "X-PT-Event",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string",
              "enum": [
                "lead.status_changed"
              ]
            }
          },
          {
            "name": "X-PT-Delivery-Id",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "X-PT-Signature",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string",
              "example": "t=1760000000,v1=5f1c…e9a2"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/WebhookEnvelope"
              }
            }
          }
        },
        "responses": {
          "2xx": {
            "description": "Recebido"
          }
        }
      }
    },
    "appointment.proposed": {
      "post": {
        "summary": "Agendamento proposto",
        "description": "Um cliente propõe um agendamento que a oficina ainda precisa confirmar.\n\nEntrega POST assinada com X-PT-Signature (HMAC-SHA256 de \"<t>.<corpo>\"). Responda 2xx em menos de 5 s.",
        "tags": [
          "Agendamentos"
        ],
        "parameters": [
          {
            "name": "X-PT-Event",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string",
              "enum": [
                "appointment.proposed"
              ]
            }
          },
          {
            "name": "X-PT-Delivery-Id",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "X-PT-Signature",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string",
              "example": "t=1760000000,v1=5f1c…e9a2"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/WebhookEnvelope"
              }
            }
          }
        },
        "responses": {
          "2xx": {
            "description": "Recebido"
          }
        }
      }
    },
    "appointment.confirmed": {
      "post": {
        "summary": "Agendamento confirmado",
        "description": "Um agendamento fica confirmado na agenda.\n\nEntrega POST assinada com X-PT-Signature (HMAC-SHA256 de \"<t>.<corpo>\"). Responda 2xx em menos de 5 s.",
        "tags": [
          "Agendamentos"
        ],
        "parameters": [
          {
            "name": "X-PT-Event",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string",
              "enum": [
                "appointment.confirmed"
              ]
            }
          },
          {
            "name": "X-PT-Delivery-Id",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "X-PT-Signature",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string",
              "example": "t=1760000000,v1=5f1c…e9a2"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/WebhookEnvelope"
              }
            }
          }
        },
        "responses": {
          "2xx": {
            "description": "Recebido"
          }
        }
      }
    },
    "appointment.cancelled": {
      "post": {
        "summary": "Agendamento cancelado",
        "description": "O agendamento de um orçamento foi cancelado.\n\nEntrega POST assinada com X-PT-Signature (HMAC-SHA256 de \"<t>.<corpo>\"). Responda 2xx em menos de 5 s.",
        "tags": [
          "Agendamentos"
        ],
        "parameters": [
          {
            "name": "X-PT-Event",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string",
              "enum": [
                "appointment.cancelled"
              ]
            }
          },
          {
            "name": "X-PT-Delivery-Id",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "X-PT-Signature",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string",
              "example": "t=1760000000,v1=5f1c…e9a2"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/WebhookEnvelope"
              }
            }
          }
        },
        "responses": {
          "2xx": {
            "description": "Recebido"
          }
        }
      }
    },
    "vehicle.checked_in": {
      "post": {
        "summary": "Veículo recebido",
        "description": "O veículo entrou na oficina (recepção ou sem agendamento).\n\nEntrega POST assinada com X-PT-Signature (HMAC-SHA256 de \"<t>.<corpo>\"). Responda 2xx em menos de 5 s.",
        "tags": [
          "Veículos"
        ],
        "parameters": [
          {
            "name": "X-PT-Event",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string",
              "enum": [
                "vehicle.checked_in"
              ]
            }
          },
          {
            "name": "X-PT-Delivery-Id",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "X-PT-Signature",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string",
              "example": "t=1760000000,v1=5f1c…e9a2"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/WebhookEnvelope"
              }
            }
          }
        },
        "responses": {
          "2xx": {
            "description": "Recebido"
          }
        }
      }
    },
    "vehicle.ready": {
      "post": {
        "summary": "Veículo pronto",
        "description": "O reparo terminou e o veículo está pronto para retirada.\n\nEntrega POST assinada com X-PT-Signature (HMAC-SHA256 de \"<t>.<corpo>\"). Responda 2xx em menos de 5 s.",
        "tags": [
          "Veículos"
        ],
        "parameters": [
          {
            "name": "X-PT-Event",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string",
              "enum": [
                "vehicle.ready"
              ]
            }
          },
          {
            "name": "X-PT-Delivery-Id",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "X-PT-Signature",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string",
              "example": "t=1760000000,v1=5f1c…e9a2"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/WebhookEnvelope"
              }
            }
          }
        },
        "responses": {
          "2xx": {
            "description": "Recebido"
          }
        }
      }
    },
    "vehicle.delivered": {
      "post": {
        "summary": "Veículo entregue",
        "description": "O cliente retirou o veículo.\n\nEntrega POST assinada com X-PT-Signature (HMAC-SHA256 de \"<t>.<corpo>\"). Responda 2xx em menos de 5 s.",
        "tags": [
          "Veículos"
        ],
        "parameters": [
          {
            "name": "X-PT-Event",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string",
              "enum": [
                "vehicle.delivered"
              ]
            }
          },
          {
            "name": "X-PT-Delivery-Id",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "X-PT-Signature",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string",
              "example": "t=1760000000,v1=5f1c…e9a2"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/WebhookEnvelope"
              }
            }
          }
        },
        "responses": {
          "2xx": {
            "description": "Recebido"
          }
        }
      }
    },
    "invoice.issued": {
      "post": {
        "summary": "Fatura emitida",
        "description": "Uma fatura foi emitida com número definitivo.\n\nEntrega POST assinada com X-PT-Signature (HMAC-SHA256 de \"<t>.<corpo>\"). Responda 2xx em menos de 5 s.",
        "tags": [
          "Faturas"
        ],
        "parameters": [
          {
            "name": "X-PT-Event",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string",
              "enum": [
                "invoice.issued"
              ]
            }
          },
          {
            "name": "X-PT-Delivery-Id",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "X-PT-Signature",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string",
              "example": "t=1760000000,v1=5f1c…e9a2"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/WebhookEnvelope"
              }
            }
          }
        },
        "responses": {
          "2xx": {
            "description": "Recebido"
          }
        }
      }
    },
    "payment.received": {
      "post": {
        "summary": "Recebimento registrado",
        "description": "Um recebimento foi lançado em uma fatura.\n\nEntrega POST assinada com X-PT-Signature (HMAC-SHA256 de \"<t>.<corpo>\"). Responda 2xx em menos de 5 s.",
        "tags": [
          "Faturas"
        ],
        "parameters": [
          {
            "name": "X-PT-Event",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string",
              "enum": [
                "payment.received"
              ]
            }
          },
          {
            "name": "X-PT-Delivery-Id",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "X-PT-Signature",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string",
              "example": "t=1760000000,v1=5f1c…e9a2"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/WebhookEnvelope"
              }
            }
          }
        },
        "responses": {
          "2xx": {
            "description": "Recebido"
          }
        }
      }
    },
    "client.created": {
      "post": {
        "summary": "Cliente criado",
        "description": "Um cliente foi cadastrado.\n\nEntrega POST assinada com X-PT-Signature (HMAC-SHA256 de \"<t>.<corpo>\"). Responda 2xx em menos de 5 s.",
        "tags": [
          "Clientes"
        ],
        "parameters": [
          {
            "name": "X-PT-Event",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string",
              "enum": [
                "client.created"
              ]
            }
          },
          {
            "name": "X-PT-Delivery-Id",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "X-PT-Signature",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string",
              "example": "t=1760000000,v1=5f1c…e9a2"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/WebhookEnvelope"
              }
            }
          }
        },
        "responses": {
          "2xx": {
            "description": "Recebido"
          }
        }
      }
    },
    "client.updated": {
      "post": {
        "summary": "Cliente atualizado",
        "description": "Os dados de um cliente foram alterados.\n\nEntrega POST assinada com X-PT-Signature (HMAC-SHA256 de \"<t>.<corpo>\"). Responda 2xx em menos de 5 s.",
        "tags": [
          "Clientes"
        ],
        "parameters": [
          {
            "name": "X-PT-Event",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string",
              "enum": [
                "client.updated"
              ]
            }
          },
          {
            "name": "X-PT-Delivery-Id",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "X-PT-Signature",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string",
              "example": "t=1760000000,v1=5f1c…e9a2"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/WebhookEnvelope"
              }
            }
          }
        },
        "responses": {
          "2xx": {
            "description": "Recebido"
          }
        }
      }
    },
    "communication.inbound": {
      "post": {
        "summary": "Mensagem recebida",
        "description": "Chegou um e-mail, SMS, WhatsApp ou ligação de um cliente.\n\nEntrega POST assinada com X-PT-Signature (HMAC-SHA256 de \"<t>.<corpo>\"). Responda 2xx em menos de 5 s.",
        "tags": [
          "Comunicações"
        ],
        "parameters": [
          {
            "name": "X-PT-Event",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string",
              "enum": [
                "communication.inbound"
              ]
            }
          },
          {
            "name": "X-PT-Delivery-Id",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "X-PT-Signature",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string",
              "example": "t=1760000000,v1=5f1c…e9a2"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/WebhookEnvelope"
              }
            }
          }
        },
        "responses": {
          "2xx": {
            "description": "Recebido"
          }
        }
      }
    },
    "email.sent": {
      "post": {
        "summary": "E-mail enviado",
        "description": "Um e-mail foi enviado (campanhas, avisos ou API).\n\nEntrega POST assinada com X-PT-Signature (HMAC-SHA256 de \"<t>.<corpo>\"). Responda 2xx em menos de 5 s.",
        "tags": [
          "Comunicações"
        ],
        "parameters": [
          {
            "name": "X-PT-Event",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string",
              "enum": [
                "email.sent"
              ]
            }
          },
          {
            "name": "X-PT-Delivery-Id",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "X-PT-Signature",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string",
              "example": "t=1760000000,v1=5f1c…e9a2"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/WebhookEnvelope"
              }
            }
          }
        },
        "responses": {
          "2xx": {
            "description": "Recebido"
          }
        }
      }
    },
    "email.failed": {
      "post": {
        "summary": "E-mail com falha",
        "description": "Não foi possível enviar um e-mail.\n\nEntrega POST assinada com X-PT-Signature (HMAC-SHA256 de \"<t>.<corpo>\"). Responda 2xx em menos de 5 s.",
        "tags": [
          "Comunicações"
        ],
        "parameters": [
          {
            "name": "X-PT-Event",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string",
              "enum": [
                "email.failed"
              ]
            }
          },
          {
            "name": "X-PT-Delivery-Id",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "X-PT-Signature",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string",
              "example": "t=1760000000,v1=5f1c…e9a2"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/WebhookEnvelope"
              }
            }
          }
        },
        "responses": {
          "2xx": {
            "description": "Recebido"
          }
        }
      }
    },
    "email.opened": {
      "post": {
        "summary": "E-mail aberto",
        "description": "O destinatário abriu o e-mail.\n\nEntrega POST assinada com X-PT-Signature (HMAC-SHA256 de \"<t>.<corpo>\"). Responda 2xx em menos de 5 s.",
        "tags": [
          "Comunicações"
        ],
        "parameters": [
          {
            "name": "X-PT-Event",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string",
              "enum": [
                "email.opened"
              ]
            }
          },
          {
            "name": "X-PT-Delivery-Id",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "X-PT-Signature",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string",
              "example": "t=1760000000,v1=5f1c…e9a2"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/WebhookEnvelope"
              }
            }
          }
        },
        "responses": {
          "2xx": {
            "description": "Recebido"
          }
        }
      }
    },
    "email.clicked": {
      "post": {
        "summary": "Clique no e-mail",
        "description": "O destinatário clicou em um link do e-mail.\n\nEntrega POST assinada com X-PT-Signature (HMAC-SHA256 de \"<t>.<corpo>\"). Responda 2xx em menos de 5 s.",
        "tags": [
          "Comunicações"
        ],
        "parameters": [
          {
            "name": "X-PT-Event",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string",
              "enum": [
                "email.clicked"
              ]
            }
          },
          {
            "name": "X-PT-Delivery-Id",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "X-PT-Signature",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string",
              "example": "t=1760000000,v1=5f1c…e9a2"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/WebhookEnvelope"
              }
            }
          }
        },
        "responses": {
          "2xx": {
            "description": "Recebido"
          }
        }
      }
    },
    "email.unsubscribed": {
      "post": {
        "summary": "Descadastro de e-mail",
        "description": "O destinatário cancelou o recebimento de e-mails.\n\nEntrega POST assinada com X-PT-Signature (HMAC-SHA256 de \"<t>.<corpo>\"). Responda 2xx em menos de 5 s.",
        "tags": [
          "Comunicações"
        ],
        "parameters": [
          {
            "name": "X-PT-Event",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string",
              "enum": [
                "email.unsubscribed"
              ]
            }
          },
          {
            "name": "X-PT-Delivery-Id",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "X-PT-Signature",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string",
              "example": "t=1760000000,v1=5f1c…e9a2"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/WebhookEnvelope"
              }
            }
          }
        },
        "responses": {
          "2xx": {
            "description": "Recebido"
          }
        }
      }
    },
    "email.bounced": {
      "post": {
        "summary": "E-mail devolvido",
        "description": "O e-mail foi devolvido.\n\nEntrega POST assinada com X-PT-Signature (HMAC-SHA256 de \"<t>.<corpo>\"). Responda 2xx em menos de 5 s.",
        "tags": [
          "Comunicações"
        ],
        "parameters": [
          {
            "name": "X-PT-Event",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string",
              "enum": [
                "email.bounced"
              ]
            }
          },
          {
            "name": "X-PT-Delivery-Id",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "X-PT-Signature",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string",
              "example": "t=1760000000,v1=5f1c…e9a2"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/WebhookEnvelope"
              }
            }
          }
        },
        "responses": {
          "2xx": {
            "description": "Recebido"
          }
        }
      }
    },
    "sms.sent": {
      "post": {
        "summary": "SMS enviado",
        "description": "Um SMS foi enviado.\n\nEntrega POST assinada com X-PT-Signature (HMAC-SHA256 de \"<t>.<corpo>\"). Responda 2xx em menos de 5 s.",
        "tags": [
          "Comunicações"
        ],
        "parameters": [
          {
            "name": "X-PT-Event",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string",
              "enum": [
                "sms.sent"
              ]
            }
          },
          {
            "name": "X-PT-Delivery-Id",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "X-PT-Signature",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string",
              "example": "t=1760000000,v1=5f1c…e9a2"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/WebhookEnvelope"
              }
            }
          }
        },
        "responses": {
          "2xx": {
            "description": "Recebido"
          }
        }
      }
    },
    "sms.failed": {
      "post": {
        "summary": "SMS com falha",
        "description": "Não foi possível enviar um SMS.\n\nEntrega POST assinada com X-PT-Signature (HMAC-SHA256 de \"<t>.<corpo>\"). Responda 2xx em menos de 5 s.",
        "tags": [
          "Comunicações"
        ],
        "parameters": [
          {
            "name": "X-PT-Event",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string",
              "enum": [
                "sms.failed"
              ]
            }
          },
          {
            "name": "X-PT-Delivery-Id",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "X-PT-Signature",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string",
              "example": "t=1760000000,v1=5f1c…e9a2"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/WebhookEnvelope"
              }
            }
          }
        },
        "responses": {
          "2xx": {
            "description": "Recebido"
          }
        }
      }
    },
    "sms.unsubscribed": {
      "post": {
        "summary": "Descadastro de SMS",
        "description": "O destinatário pediu para não receber SMS.\n\nEntrega POST assinada com X-PT-Signature (HMAC-SHA256 de \"<t>.<corpo>\"). Responda 2xx em menos de 5 s.",
        "tags": [
          "Comunicações"
        ],
        "parameters": [
          {
            "name": "X-PT-Event",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string",
              "enum": [
                "sms.unsubscribed"
              ]
            }
          },
          {
            "name": "X-PT-Delivery-Id",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "X-PT-Signature",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string",
              "example": "t=1760000000,v1=5f1c…e9a2"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/WebhookEnvelope"
              }
            }
          }
        },
        "responses": {
          "2xx": {
            "description": "Recebido"
          }
        }
      }
    },
    "whatsapp.unsubscribed": {
      "post": {
        "summary": "Descadastro de WhatsApp",
        "description": "O destinatário pediu para não receber WhatsApp.\n\nEntrega POST assinada com X-PT-Signature (HMAC-SHA256 de \"<t>.<corpo>\"). Responda 2xx em menos de 5 s.",
        "tags": [
          "Comunicações"
        ],
        "parameters": [
          {
            "name": "X-PT-Event",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string",
              "enum": [
                "whatsapp.unsubscribed"
              ]
            }
          },
          {
            "name": "X-PT-Delivery-Id",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "X-PT-Signature",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string",
              "example": "t=1760000000,v1=5f1c…e9a2"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/WebhookEnvelope"
              }
            }
          }
        },
        "responses": {
          "2xx": {
            "description": "Recebido"
          }
        }
      }
    },
    "campaign.finished": {
      "post": {
        "summary": "Campanha concluída",
        "description": "Uma campanha terminou de ser enviada.\n\nEntrega POST assinada com X-PT-Signature (HMAC-SHA256 de \"<t>.<corpo>\"). Responda 2xx em menos de 5 s.",
        "tags": [
          "Comunicações"
        ],
        "parameters": [
          {
            "name": "X-PT-Event",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string",
              "enum": [
                "campaign.finished"
              ]
            }
          },
          {
            "name": "X-PT-Delivery-Id",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "X-PT-Signature",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string",
              "example": "t=1760000000,v1=5f1c…e9a2"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/WebhookEnvelope"
              }
            }
          }
        },
        "responses": {
          "2xx": {
            "description": "Recebido"
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "bearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "bearerFormat": "pt_live_… / pt_test_…"
      },
      "apiKeyAuth": {
        "type": "apiKey",
        "in": "header",
        "name": "X-Api-Key"
      }
    },
    "schemas": {
      "Error": {
        "type": "object",
        "properties": {
          "error": {
            "type": "object",
            "properties": {
              "code": {
                "type": "string",
                "description": "Código estável do erro",
                "example": "insufficient_scope"
              },
              "message": {
                "type": "string",
                "description": "Mensagem para pessoas (pode mudar)",
                "example": "A chave de API não tem a permissão «budgets:read»."
              }
            },
            "required": [
              "code",
              "message"
            ],
            "additionalProperties": false
          }
        },
        "description": "Formato uniforme de erro",
        "required": [
          "error"
        ],
        "additionalProperties": false
      },
      "ValidationError": {
        "type": "object",
        "properties": {
          "error": {
            "type": "object",
            "properties": {
              "code": {
                "type": "string",
                "enum": [
                  "validation_error"
                ],
                "example": "validation_error"
              },
              "message": {
                "type": "string",
                "example": "O corpo da requisição não é válido: confira a lista «fields»."
              },
              "fields": {
                "type": "array",
                "items": {
                  "type": "object",
                  "properties": {
                    "path": {
                      "type": "string",
                      "description": "Caminho do campo (lines[0].quantity)",
                      "example": "phone"
                    },
                    "message": {
                      "type": "string",
                      "example": "O formato não é válido."
                    }
                  },
                  "required": [
                    "path",
                    "message"
                  ],
                  "additionalProperties": false
                }
              }
            },
            "required": [
              "code",
              "message",
              "fields"
            ],
            "additionalProperties": false
          }
        },
        "description": "422: corpo que não segue o esquema",
        "required": [
          "error"
        ],
        "additionalProperties": false
      },
      "Page": {
        "type": "object",
        "properties": {
          "next_cursor": {
            "type": [
              "string",
              "null"
            ],
            "description": "Cursor opaco para pedir a próxima página; null se não houver mais",
            "example": "aWQ6MTIzNA"
          },
          "has_more": {
            "type": "boolean",
            "description": "true se ainda houver resultados",
            "example": true
          }
        },
        "description": "Campos de paginação que acompanham `data` em todas as listagens",
        "required": [
          "next_cursor",
          "has_more"
        ],
        "additionalProperties": false
      },
      "StaffRef": {
        "type": "object",
        "properties": {
          "id": {
            "type": "integer",
            "description": "Id do usuário da oficina",
            "example": 3
          },
          "name": {
            "type": [
              "string",
              "null"
            ],
            "description": "Nome exibido",
            "example": "Marta"
          }
        },
        "description": "Pessoa da oficina: só id e nome",
        "required": [
          "id",
          "name"
        ],
        "additionalProperties": false
      },
      "ClientRef": {
        "type": "object",
        "properties": {
          "id": {
            "type": "integer",
            "example": 1204
          },
          "name": {
            "type": [
              "string",
              "null"
            ],
            "example": "Laura Gómez"
          }
        },
        "required": [
          "id",
          "name"
        ],
        "additionalProperties": false
      },
      "VehicleRef": {
        "type": "object",
        "properties": {
          "id": {
            "type": "integer",
            "example": 871
          },
          "plate": {
            "type": [
              "string",
              "null"
            ],
            "example": "1234 KLM"
          },
          "brand": {
            "type": [
              "string",
              "null"
            ],
            "example": "Seat"
          },
          "model": {
            "type": [
              "string",
              "null"
            ],
            "example": "León"
          }
        },
        "required": [
          "id",
          "plate",
          "brand",
          "model"
        ],
        "additionalProperties": false
      },
      "BoxRef": {
        "type": "object",
        "properties": {
          "id": {
            "type": "integer",
            "example": 2
          },
          "name": {
            "type": [
              "string",
              "null"
            ],
            "example": "Elevador 2"
          }
        },
        "required": [
          "id",
          "name"
        ],
        "additionalProperties": false
      },
      "CategoryRef": {
        "type": "object",
        "properties": {
          "id": {
            "type": "integer",
            "example": 5
          },
          "name": {
            "type": [
              "string",
              "null"
            ],
            "example": "Mantenimiento"
          }
        },
        "required": [
          "id",
          "name"
        ],
        "additionalProperties": false
      },
      "Client": {
        "type": "object",
        "properties": {
          "id": {
            "type": "integer",
            "example": 1204
          },
          "name": {
            "type": [
              "string",
              "null"
            ],
            "example": "Laura Gómez"
          },
          "phone": {
            "type": [
              "string",
              "null"
            ],
            "description": "Telefone principal em formato internacional quando conhecido",
            "example": "+34600111222"
          },
          "phone_secondary": {
            "type": [
              "string",
              "null"
            ],
            "example": null
          },
          "whatsapp_phone": {
            "type": [
              "string",
              "null"
            ],
            "example": null
          },
          "email": {
            "type": [
              "string",
              "null"
            ],
            "example": "laura@ejemplo.com"
          },
          "billing_email": {
            "type": [
              "string",
              "null"
            ],
            "example": null
          },
          "tax_id": {
            "type": [
              "string",
              "null"
            ],
            "description": "NIF/CIF",
            "example": "12345678Z"
          },
          "address": {
            "type": [
              "string",
              "null"
            ],
            "example": "C/ Mayor 12"
          },
          "city": {
            "type": [
              "string",
              "null"
            ],
            "example": "Barcelona"
          },
          "zip": {
            "type": [
              "string",
              "null"
            ],
            "example": "08001"
          },
          "province": {
            "type": [
              "string",
              "null"
            ],
            "example": "Barcelona"
          },
          "country": {
            "type": [
              "string",
              "null"
            ],
            "example": "ES"
          },
          "preferred_language": {
            "type": [
              "string",
              "null"
            ],
            "description": "Idioma das comunicações (es, ca, en, pt, fr, bg)",
            "example": "es"
          },
          "preferred_contact_method": {
            "type": [
              "string",
              "null"
            ],
            "example": "whatsapp"
          },
          "vip": {
            "type": "boolean",
            "example": false
          },
          "marketing_opt_out": {
            "type": "boolean",
            "description": "Não quer comunicações de marketing",
            "example": false
          },
          "channel_opt_out": {
            "type": "object",
            "properties": {
              "email": {
                "type": "boolean",
                "example": false
              },
              "sms": {
                "type": "boolean",
                "example": false
              },
              "whatsapp": {
                "type": "boolean",
                "example": false
              },
              "call": {
                "type": "boolean",
                "example": false
              }
            },
            "description": "Canais que o cliente pediu para não usar",
            "required": [
              "email",
              "sms",
              "whatsapp",
              "call"
            ],
            "additionalProperties": false
          },
          "erased_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "Data do pedido de exclusão (RGPD); os dados pessoais já estão anonimizados",
            "example": null
          },
          "vehicles": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Vehicle"
            },
            "description": "Só com ?expand=vehicles"
          }
        },
        "description": "Cliente",
        "required": [
          "id",
          "name",
          "phone",
          "phone_secondary",
          "whatsapp_phone",
          "email",
          "billing_email",
          "tax_id",
          "address",
          "city",
          "zip",
          "province",
          "country",
          "preferred_language",
          "preferred_contact_method",
          "vip",
          "marketing_opt_out",
          "channel_opt_out",
          "erased_at",
          "vehicles"
        ],
        "additionalProperties": false
      },
      "Vehicle": {
        "type": "object",
        "properties": {
          "id": {
            "type": "integer",
            "example": 871
          },
          "client_id": {
            "type": [
              "integer",
              "null"
            ],
            "example": 1204
          },
          "client": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/ClientRef"
              },
              {
                "type": "null"
              }
            ]
          },
          "plate": {
            "type": [
              "string",
              "null"
            ],
            "example": "1234 KLM"
          },
          "vin": {
            "type": [
              "string",
              "null"
            ],
            "description": "Número do chassi",
            "example": "VSSZZZ5FZJR123456"
          },
          "brand": {
            "type": [
              "string",
              "null"
            ],
            "example": "Seat"
          },
          "model": {
            "type": [
              "string",
              "null"
            ],
            "example": "León"
          },
          "variant": {
            "type": [
              "string",
              "null"
            ],
            "example": "1.5 TSI"
          },
          "year": {
            "type": [
              "integer",
              "null"
            ],
            "example": 2019
          },
          "registration_date": {
            "type": [
              "string",
              "null"
            ],
            "format": "date",
            "example": "2019-03-15"
          },
          "fuel": {
            "type": [
              "string",
              "null"
            ],
            "example": "Gasolina"
          },
          "transmission": {
            "type": [
              "string",
              "null"
            ],
            "example": "Manual"
          },
          "engine_code": {
            "type": [
              "string",
              "null"
            ],
            "example": "DADA"
          },
          "horsepower": {
            "type": [
              "integer",
              "null"
            ],
            "example": 130
          },
          "displacement": {
            "type": [
              "string",
              "null"
            ],
            "example": "1498"
          },
          "color_code": {
            "type": [
              "string",
              "null"
            ],
            "example": null
          },
          "environmental_label": {
            "type": [
              "string",
              "null"
            ],
            "description": "Selo ambiental DGT da Espanha",
            "example": "C"
          },
          "km": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Última quilometragem registrada em um orçamento",
            "example": 84500
          },
          "itv_expiry_date": {
            "type": [
              "string",
              "null"
            ],
            "format": "date",
            "description": "Vencimento da inspeção técnica (ITV)",
            "example": "2027-03-15"
          },
          "status": {
            "type": "string",
            "description": "activo, baja_temporal ou baja",
            "example": "activo"
          }
        },
        "description": "Vehículo",
        "required": [
          "id",
          "client_id",
          "client",
          "plate",
          "vin",
          "brand",
          "model",
          "variant",
          "year",
          "registration_date",
          "fuel",
          "transmission",
          "engine_code",
          "horsepower",
          "displacement",
          "color_code",
          "environmental_label",
          "km",
          "itv_expiry_date",
          "status"
        ],
        "additionalProperties": false
      },
      "BudgetLine": {
        "type": "object",
        "properties": {
          "id": {
            "type": "integer",
            "example": 5501
          },
          "description": {
            "type": [
              "string",
              "null"
            ],
            "example": "Troca de óleo e filtro"
          },
          "extended_detail": {
            "type": [
              "string",
              "null"
            ],
            "description": "Detalhe ampliado visível para o cliente",
            "example": null
          },
          "reference": {
            "type": [
              "string",
              "null"
            ],
            "description": "Código da peça",
            "example": null
          },
          "line_type": {
            "type": [
              "string",
              "null"
            ],
            "description": "labor, part, diagnosis, other…",
            "example": "labor"
          },
          "group_title": {
            "type": [
              "string",
              "null"
            ],
            "description": "Título do grupo de itens",
            "example": null
          },
          "quantity": {
            "type": "number",
            "example": 1
          },
          "unit_price": {
            "type": "number",
            "description": "Preço unitário de venda sem impostos",
            "example": 65
          },
          "discount_pct": {
            "type": "number",
            "description": "Desconto em porcentagem",
            "example": 0
          },
          "tax_rate": {
            "type": "number",
            "description": "Alíquota aplicada (IVA/IGIC)",
            "example": 21
          },
          "tax_exempt_code": {
            "type": [
              "string",
              "null"
            ],
            "example": null
          },
          "price_estimated": {
            "type": "boolean",
            "description": "Preço a confirmar",
            "example": false
          },
          "base": {
            "type": "number",
            "description": "Base do item com o desconto aplicado",
            "example": 65
          },
          "currency": {
            "type": "string",
            "enum": [
              "EUR"
            ],
            "example": "EUR"
          },
          "sort_order": {
            "type": "integer",
            "example": 0
          }
        },
        "description": "Item de orçamento (nunca inclui o custo)",
        "required": [
          "id",
          "description",
          "extended_detail",
          "reference",
          "line_type",
          "group_title",
          "quantity",
          "unit_price",
          "discount_pct",
          "tax_rate",
          "tax_exempt_code",
          "price_estimated",
          "base",
          "currency",
          "sort_order"
        ],
        "additionalProperties": false
      },
      "Totals": {
        "type": "object",
        "properties": {
          "base": {
            "type": "number",
            "description": "Valor em euros com duas casas decimais",
            "example": 65
          },
          "tax": {
            "type": "number",
            "description": "Valor em euros com duas casas decimais",
            "example": 13.65
          },
          "total": {
            "type": "number",
            "description": "Valor em euros com duas casas decimais",
            "example": 78.65
          },
          "currency": {
            "type": "string",
            "enum": [
              "EUR"
            ],
            "example": "EUR"
          },
          "tax_rates": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "rate": {
                  "type": "number",
                  "example": 21
                },
                "base": {
                  "type": "number",
                  "description": "Valor em euros com duas casas decimais",
                  "example": 65
                },
                "quota": {
                  "type": "number",
                  "description": "Valor em euros com duas casas decimais",
                  "example": 13.65
                }
              },
              "required": [
                "rate",
                "base",
                "quota"
              ],
              "additionalProperties": false
            }
          }
        },
        "description": "Totais detalhados por alíquota",
        "required": [
          "base",
          "tax",
          "total",
          "currency",
          "tax_rates"
        ],
        "additionalProperties": false
      },
      "Appointment": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "Identificador do agendamento: b<orçamento> se confirmado, p<proposta> se pendente",
            "example": "b1234"
          },
          "budget_id": {
            "type": "integer",
            "example": 1234
          },
          "status": {
            "type": "string",
            "enum": [
              "confirmed",
              "proposed"
            ],
            "example": "confirmed"
          },
          "start": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "example": "2026-10-06T09:30:00.000Z"
          },
          "end": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "example": "2026-10-06T10:30:00.000Z"
          },
          "estimated_duration_minutes": {
            "type": [
              "integer",
              "null"
            ],
            "example": 60
          },
          "client_confirmed_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "Quando o cliente confirmou (null se só a oficina marcou)",
            "example": null
          },
          "budget_status": {
            "type": [
              "string",
              "null"
            ],
            "example": "Aprobado"
          },
          "checked_in_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "Entrada real do veículo",
            "example": null
          },
          "client": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/ClientRef"
              },
              {
                "type": "null"
              }
            ]
          },
          "vehicle": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/VehicleRef"
              },
              {
                "type": "null"
              }
            ]
          },
          "mechanic": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/StaffRef"
              },
              {
                "type": "null"
              }
            ]
          },
          "box": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/BoxRef"
              },
              {
                "type": "null"
              }
            ]
          },
          "category": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/CategoryRef"
              },
              {
                "type": "null"
              }
            ]
          },
          "channel": {
            "type": [
              "string",
              "null"
            ],
            "description": "Canal pelo qual a proposta chegou (web, portal, voice…)",
            "example": null
          },
          "proposed_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "Quando foi proposto (só status=proposed)",
            "example": null
          }
        },
        "description": "Agendamento confirmado ou proposta pendente",
        "required": [
          "id",
          "budget_id",
          "status",
          "start",
          "end",
          "estimated_duration_minutes",
          "client_confirmed_at",
          "budget_status",
          "checked_in_at",
          "client",
          "vehicle",
          "mechanic",
          "box",
          "category",
          "channel",
          "proposed_at"
        ],
        "additionalProperties": false
      },
      "BudgetAppointment": {
        "type": "object",
        "properties": {
          "start": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "example": "2026-10-06T09:30:00.000Z"
          },
          "end": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "example": "2026-10-06T10:30:00.000Z"
          },
          "status": {
            "type": "string",
            "enum": [
              "confirmed"
            ],
            "example": "confirmed"
          },
          "client_confirmed_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "example": null
          },
          "estimated_duration_minutes": {
            "type": [
              "integer",
              "null"
            ],
            "example": 60
          },
          "box": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/BoxRef"
              },
              {
                "type": "null"
              }
            ]
          }
        },
        "required": [
          "start",
          "end",
          "status",
          "client_confirmed_at",
          "estimated_duration_minutes",
          "box"
        ],
        "additionalProperties": false
      },
      "Budget": {
        "type": "object",
        "properties": {
          "id": {
            "type": "integer",
            "example": 1234
          },
          "status": {
            "type": [
              "string",
              "null"
            ],
            "description": "Pendiente, En cotización, En curso, En espera, Enviado, Aprobado, Finalizado, Facturado, Facturado externamente, Rechazado, Cancelado, Desistido (valores em espanhol)",
            "example": "Aprobado"
          },
          "created_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "example": "2026-10-06T09:30:00.000Z"
          },
          "updated_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "example": "2026-10-06T09:30:00.000Z"
          },
          "channel": {
            "type": [
              "string",
              "null"
            ],
            "description": "Origem: site, e-mail, telefone, assistente, API…",
            "example": "web"
          },
          "client_id": {
            "type": [
              "integer",
              "null"
            ],
            "example": 1204
          },
          "client": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/ClientRef"
              },
              {
                "type": "null"
              }
            ]
          },
          "vehicle_id": {
            "type": [
              "integer",
              "null"
            ],
            "example": 871
          },
          "vehicle": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/VehicleRef"
              },
              {
                "type": "null"
              }
            ]
          },
          "category": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/CategoryRef"
              },
              {
                "type": "null"
              }
            ]
          },
          "subcategory": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/CategoryRef"
              },
              {
                "type": "null"
              }
            ]
          },
          "assigned_user": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/StaffRef"
              },
              {
                "type": "null"
              }
            ]
          },
          "mechanic": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/StaffRef"
              },
              {
                "type": "null"
              }
            ]
          },
          "appointment": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/BudgetAppointment"
              },
              {
                "type": "null"
              }
            ]
          },
          "date_in": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "Entrada do veículo na oficina",
            "example": null
          },
          "date_out": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "Saída do veículo",
            "example": null
          },
          "km": {
            "type": [
              "integer",
              "null"
            ],
            "example": 84500
          },
          "client_reference": {
            "type": [
              "string",
              "null"
            ],
            "description": "Referência que o cliente quer ver na fatura",
            "example": null
          },
          "waiting_parts": {
            "type": "boolean",
            "example": false
          },
          "on_hold": {
            "type": "boolean",
            "example": false
          },
          "hold_until": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "example": null
          },
          "client_signed_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "Assinatura do cliente ao aprovar",
            "example": null
          },
          "delivered_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "Entrega do veículo ao cliente",
            "example": null
          },
          "reject_reason": {
            "type": [
              "string",
              "null"
            ],
            "example": null
          },
          "tracking_url": {
            "type": [
              "string",
              "null"
            ],
            "description": "URL pública de acompanhamento assinada",
            "example": "https://taller.ejemplo.com/public/seguimiento?id=…"
          }
        },
        "description": "Orçamento (resumo)",
        "required": [
          "id",
          "status",
          "created_at",
          "updated_at",
          "channel",
          "client_id",
          "client",
          "vehicle_id",
          "vehicle",
          "category",
          "subcategory",
          "assigned_user",
          "mechanic",
          "appointment",
          "date_in",
          "date_out",
          "km",
          "client_reference",
          "waiting_parts",
          "on_hold",
          "hold_until",
          "client_signed_at",
          "delivered_at",
          "reject_reason",
          "tracking_url"
        ],
        "additionalProperties": false
      },
      "BudgetDetail": {
        "type": "object",
        "properties": {
          "id": {
            "type": "integer",
            "example": 1234
          },
          "status": {
            "type": [
              "string",
              "null"
            ],
            "example": "Aprobado"
          },
          "created_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "example": "2026-10-06T09:30:00.000Z"
          },
          "updated_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "example": "2026-10-06T09:30:00.000Z"
          },
          "channel": {
            "type": [
              "string",
              "null"
            ],
            "example": "web"
          },
          "client_id": {
            "type": [
              "integer",
              "null"
            ],
            "example": 1204
          },
          "client": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/ClientRef"
              },
              {
                "type": "null"
              }
            ]
          },
          "vehicle_id": {
            "type": [
              "integer",
              "null"
            ],
            "example": 871
          },
          "vehicle": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/VehicleRef"
              },
              {
                "type": "null"
              }
            ]
          },
          "category": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/CategoryRef"
              },
              {
                "type": "null"
              }
            ]
          },
          "subcategory": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/CategoryRef"
              },
              {
                "type": "null"
              }
            ]
          },
          "assigned_user": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/StaffRef"
              },
              {
                "type": "null"
              }
            ]
          },
          "mechanic": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/StaffRef"
              },
              {
                "type": "null"
              }
            ]
          },
          "appointment": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/BudgetAppointment"
              },
              {
                "type": "null"
              }
            ]
          },
          "date_in": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "example": null
          },
          "date_out": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "example": null
          },
          "km": {
            "type": [
              "integer",
              "null"
            ],
            "example": 84500
          },
          "client_reference": {
            "type": [
              "string",
              "null"
            ],
            "example": null
          },
          "waiting_parts": {
            "type": "boolean",
            "example": false
          },
          "on_hold": {
            "type": "boolean",
            "example": false
          },
          "hold_until": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "example": null
          },
          "client_signed_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "example": null
          },
          "delivered_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "example": null
          },
          "reject_reason": {
            "type": [
              "string",
              "null"
            ],
            "example": null
          },
          "tracking_url": {
            "type": [
              "string",
              "null"
            ],
            "example": "https://taller.ejemplo.com/public/seguimiento?id=…"
          },
          "public_notes": {
            "type": [
              "string",
              "null"
            ],
            "description": "Observações visíveis para o cliente",
            "example": "Verificar também o ruído na suspensão."
          },
          "totals": {
            "$ref": "#/components/schemas/Totals"
          },
          "lines": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/BudgetLine"
            }
          }
        },
        "description": "Orçamento com itens e totais",
        "required": [
          "id",
          "status",
          "created_at",
          "updated_at",
          "channel",
          "client_id",
          "client",
          "vehicle_id",
          "vehicle",
          "category",
          "subcategory",
          "assigned_user",
          "mechanic",
          "appointment",
          "date_in",
          "date_out",
          "km",
          "client_reference",
          "waiting_parts",
          "on_hold",
          "hold_until",
          "client_signed_at",
          "delivered_at",
          "reject_reason",
          "tracking_url",
          "public_notes",
          "totals",
          "lines"
        ],
        "additionalProperties": false
      },
      "InvoiceLine": {
        "type": "object",
        "properties": {
          "id": {
            "type": "integer",
            "example": 9001
          },
          "description": {
            "type": [
              "string",
              "null"
            ],
            "example": "Troca de óleo e filtro"
          },
          "reference": {
            "type": [
              "string",
              "null"
            ],
            "example": null
          },
          "group_title": {
            "type": [
              "string",
              "null"
            ],
            "example": null
          },
          "quantity": {
            "type": "number",
            "example": 1
          },
          "unit_price": {
            "type": "number",
            "description": "Valor em euros com duas casas decimais",
            "example": 65
          },
          "discount_pct": {
            "type": "number",
            "example": 0
          },
          "tax_rate": {
            "type": "number",
            "example": 21
          },
          "tax_exempt_code": {
            "type": [
              "string",
              "null"
            ],
            "example": null
          },
          "base": {
            "type": "number",
            "description": "Valor em euros com duas casas decimais",
            "example": 65
          },
          "currency": {
            "type": "string",
            "enum": [
              "EUR"
            ],
            "example": "EUR"
          },
          "sort_order": {
            "type": "integer",
            "example": 0
          }
        },
        "required": [
          "id",
          "description",
          "reference",
          "group_title",
          "quantity",
          "unit_price",
          "discount_pct",
          "tax_rate",
          "tax_exempt_code",
          "base",
          "currency",
          "sort_order"
        ],
        "additionalProperties": false
      },
      "InvoicePayments": {
        "type": "object",
        "properties": {
          "paid": {
            "type": "number",
            "description": "Valor em euros com duas casas decimais",
            "example": 78.65
          },
          "pending": {
            "type": "number",
            "description": "Valor em euros com duas casas decimais",
            "example": 0
          },
          "settled": {
            "type": "boolean",
            "example": true
          }
        },
        "description": "Resumo de recebimentos",
        "required": [
          "paid",
          "pending",
          "settled"
        ],
        "additionalProperties": false
      },
      "Invoice": {
        "type": "object",
        "properties": {
          "id": {
            "type": "integer",
            "example": 412
          },
          "number": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Número dentro da série",
            "example": 87
          },
          "series": {
            "type": [
              "string",
              "null"
            ],
            "example": "F26"
          },
          "full_number": {
            "type": [
              "string",
              "null"
            ],
            "description": "Série + número",
            "example": "F2687"
          },
          "kind": {
            "type": "string",
            "enum": [
              "invoice",
              "rectification",
              "simplified",
              "other"
            ],
            "example": "invoice"
          },
          "rectifies_number": {
            "type": [
              "string",
              "null"
            ],
            "description": "Número da fatura retificada",
            "example": null
          },
          "status": {
            "type": "string",
            "enum": [
              "draft",
              "issued",
              "cancelled",
              "unknown"
            ],
            "example": "issued"
          },
          "date": {
            "type": [
              "string",
              "null"
            ],
            "format": "date",
            "example": "2026-10-06"
          },
          "issued_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "example": "2026-10-06T09:30:00.000Z"
          },
          "client_id": {
            "type": [
              "integer",
              "null"
            ],
            "example": 1204
          },
          "client": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/ClientRef"
              },
              {
                "type": "null"
              }
            ]
          },
          "budget_id": {
            "type": [
              "integer",
              "null"
            ],
            "example": 1234
          },
          "vehicle_id": {
            "type": [
              "integer",
              "null"
            ],
            "example": 871
          },
          "vehicle": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/VehicleRef"
              },
              {
                "type": "null"
              }
            ]
          },
          "plate": {
            "type": [
              "string",
              "null"
            ],
            "example": "1234 KLM"
          },
          "km": {
            "type": [
              "integer",
              "null"
            ],
            "example": 84500
          },
          "total": {
            "type": "number",
            "description": "Valor em euros com duas casas decimais",
            "example": 78.65
          },
          "currency": {
            "type": "string",
            "enum": [
              "EUR"
            ],
            "example": "EUR"
          },
          "payments": {
            "$ref": "#/components/schemas/InvoicePayments"
          },
          "payment_method": {
            "type": [
              "string",
              "null"
            ],
            "example": "Tarjeta"
          },
          "rebu": {
            "type": "boolean",
            "description": "Regime especial de bens usados",
            "example": false
          },
          "verifactu_hash": {
            "type": [
              "string",
              "null"
            ],
            "description": "Hash Verifactu da fatura emitida",
            "example": "3f9a…"
          }
        },
        "description": "Fatura (resumo)",
        "required": [
          "id",
          "number",
          "series",
          "full_number",
          "kind",
          "rectifies_number",
          "status",
          "date",
          "issued_at",
          "client_id",
          "client",
          "budget_id",
          "vehicle_id",
          "vehicle",
          "plate",
          "km",
          "total",
          "currency",
          "payments",
          "payment_method",
          "rebu",
          "verifactu_hash"
        ],
        "additionalProperties": false
      },
      "InvoiceDetail": {
        "type": "object",
        "properties": {
          "id": {
            "type": "integer",
            "example": 412
          },
          "number": {
            "type": [
              "integer",
              "null"
            ],
            "example": 87
          },
          "series": {
            "type": [
              "string",
              "null"
            ],
            "example": "F26"
          },
          "full_number": {
            "type": [
              "string",
              "null"
            ],
            "example": "F2687"
          },
          "kind": {
            "type": "string",
            "enum": [
              "invoice",
              "rectification",
              "simplified",
              "other"
            ],
            "example": "invoice"
          },
          "rectifies_number": {
            "type": [
              "string",
              "null"
            ],
            "example": null
          },
          "status": {
            "type": "string",
            "enum": [
              "draft",
              "issued",
              "cancelled",
              "unknown"
            ],
            "example": "issued"
          },
          "date": {
            "type": [
              "string",
              "null"
            ],
            "format": "date",
            "example": "2026-10-06"
          },
          "issued_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "example": "2026-10-06T09:30:00.000Z"
          },
          "client_id": {
            "type": [
              "integer",
              "null"
            ],
            "example": 1204
          },
          "client": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/ClientRef"
              },
              {
                "type": "null"
              }
            ]
          },
          "budget_id": {
            "type": [
              "integer",
              "null"
            ],
            "example": 1234
          },
          "vehicle_id": {
            "type": [
              "integer",
              "null"
            ],
            "example": 871
          },
          "vehicle": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/VehicleRef"
              },
              {
                "type": "null"
              }
            ]
          },
          "plate": {
            "type": [
              "string",
              "null"
            ],
            "example": "1234 KLM"
          },
          "km": {
            "type": [
              "integer",
              "null"
            ],
            "example": 84500
          },
          "total": {
            "type": "number",
            "description": "Valor em euros com duas casas decimais",
            "example": 78.65
          },
          "currency": {
            "type": "string",
            "enum": [
              "EUR"
            ],
            "example": "EUR"
          },
          "payments": {
            "$ref": "#/components/schemas/InvoicePayments"
          },
          "payment_method": {
            "type": [
              "string",
              "null"
            ],
            "example": "Tarjeta"
          },
          "rebu": {
            "type": "boolean",
            "example": false
          },
          "verifactu_hash": {
            "type": [
              "string",
              "null"
            ],
            "example": "3f9a…"
          },
          "billing": {
            "type": "object",
            "properties": {
              "name": {
                "type": [
                  "string",
                  "null"
                ],
                "example": "Laura Gómez"
              },
              "tax_id": {
                "type": [
                  "string",
                  "null"
                ],
                "example": "12345678Z"
              },
              "address": {
                "type": [
                  "string",
                  "null"
                ],
                "example": "C/ Mayor 12"
              },
              "city": {
                "type": [
                  "string",
                  "null"
                ],
                "example": "Barcelona"
              },
              "zip": {
                "type": [
                  "string",
                  "null"
                ],
                "example": "08001"
              },
              "province": {
                "type": [
                  "string",
                  "null"
                ],
                "example": "Barcelona"
              }
            },
            "description": "Dados fiscais exatamente como ficaram na fatura",
            "required": [
              "name",
              "tax_id",
              "address",
              "city",
              "zip",
              "province"
            ],
            "additionalProperties": false
          },
          "date_in": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "example": null
          },
          "date_out": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "example": null
          },
          "public_notes": {
            "type": [
              "string",
              "null"
            ],
            "example": null
          },
          "totals": {
            "$ref": "#/components/schemas/Totals"
          },
          "lines": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/InvoiceLine"
            }
          },
          "payment_list": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "id": {
                  "type": "integer",
                  "example": 77
                },
                "date": {
                  "type": [
                    "string",
                    "null"
                  ],
                  "format": "date-time",
                  "example": "2026-10-06T09:30:00.000Z"
                },
                "amount": {
                  "type": "number",
                  "description": "Valor em euros com duas casas decimais",
                  "example": 78.65
                },
                "currency": {
                  "type": "string",
                  "enum": [
                    "EUR"
                  ],
                  "example": "EUR"
                },
                "method": {
                  "type": [
                    "string",
                    "null"
                  ],
                  "example": "Tarjeta"
                }
              },
              "required": [
                "id",
                "date",
                "amount",
                "currency",
                "method"
              ],
              "additionalProperties": false
            }
          }
        },
        "description": "Fatura com itens e recebimentos",
        "required": [
          "id",
          "number",
          "series",
          "full_number",
          "kind",
          "rectifies_number",
          "status",
          "date",
          "issued_at",
          "client_id",
          "client",
          "budget_id",
          "vehicle_id",
          "vehicle",
          "plate",
          "km",
          "total",
          "currency",
          "payments",
          "payment_method",
          "rebu",
          "verifactu_hash",
          "billing",
          "date_in",
          "date_out",
          "public_notes",
          "totals",
          "lines",
          "payment_list"
        ],
        "additionalProperties": false
      },
      "ServiceCategory": {
        "type": "object",
        "properties": {
          "id": {
            "type": "integer",
            "example": 5
          },
          "name": {
            "type": [
              "string",
              "null"
            ],
            "example": "Mantenimiento"
          },
          "reference_price": {
            "type": [
              "number",
              "null"
            ],
            "description": "Preço de venda de referência, se a oficina tiver definido",
            "example": 120
          },
          "currency": {
            "type": "string",
            "enum": [
              "EUR"
            ],
            "example": "EUR"
          },
          "subcategories": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "id": {
                  "type": "integer",
                  "example": 51
                },
                "name": {
                  "type": [
                    "string",
                    "null"
                  ],
                  "example": "Troca de óleo"
                },
                "reference_price": {
                  "type": [
                    "number",
                    "null"
                  ],
                  "example": 65
                },
                "currency": {
                  "type": "string",
                  "enum": [
                    "EUR"
                  ],
                  "example": "EUR"
                }
              },
              "required": [
                "id",
                "name",
                "reference_price",
                "currency"
              ],
              "additionalProperties": false
            }
          }
        },
        "description": "Categoria de serviço com suas subcategorias",
        "required": [
          "id",
          "name",
          "reference_price",
          "currency",
          "subcategories"
        ],
        "additionalProperties": false
      },
      "LaborRate": {
        "type": "object",
        "properties": {
          "id": {
            "type": "integer",
            "example": 1
          },
          "name": {
            "type": "string",
            "example": "Mão de obra geral"
          },
          "price_per_hour": {
            "type": [
              "number",
              "null"
            ],
            "description": "Preço de venda por hora sem impostos",
            "example": 48
          },
          "currency": {
            "type": "string",
            "enum": [
              "EUR"
            ],
            "example": "EUR"
          },
          "is_default": {
            "type": "boolean",
            "example": true
          }
        },
        "description": "Tarifa de mão de obra (só preço de venda)",
        "required": [
          "id",
          "name",
          "price_per_hour",
          "currency",
          "is_default"
        ],
        "additionalProperties": false
      },
      "DaySchedule": {
        "type": "object",
        "properties": {
          "start": {
            "type": "string",
            "example": "08:00"
          },
          "end": {
            "type": "string",
            "example": "19:00"
          },
          "closed": {
            "type": "boolean",
            "example": false
          }
        },
        "required": [
          "start",
          "end",
          "closed"
        ],
        "additionalProperties": false
      },
      "Holiday": {
        "type": "object",
        "properties": {
          "date": {
            "type": "string",
            "example": "2026-10-12"
          },
          "name": {
            "type": "string",
            "example": "Fiesta Nacional de España"
          },
          "scope": {
            "type": "string",
            "enum": [
              "nacional",
              "autonomico",
              "local"
            ],
            "example": "nacional"
          }
        },
        "required": [
          "date",
          "name",
          "scope"
        ],
        "additionalProperties": false
      },
      "Workshop": {
        "type": "object",
        "properties": {
          "name": {
            "type": [
              "string",
              "null"
            ],
            "example": "Taller Ejemplo"
          },
          "legal_name": {
            "type": [
              "string",
              "null"
            ],
            "example": "Taller Ejemplo S.L."
          },
          "tax_id": {
            "type": [
              "string",
              "null"
            ],
            "example": "B12345678"
          },
          "address": {
            "type": [
              "string",
              "null"
            ],
            "example": "C/ Industria 4"
          },
          "city": {
            "type": [
              "string",
              "null"
            ],
            "example": "Barcelona"
          },
          "zip": {
            "type": [
              "string",
              "null"
            ],
            "example": "08020"
          },
          "province": {
            "type": [
              "string",
              "null"
            ],
            "example": "Barcelona"
          },
          "phone": {
            "type": [
              "string",
              "null"
            ],
            "example": "+34931234567"
          },
          "whatsapp": {
            "type": [
              "string",
              "null"
            ],
            "example": "+34600111222"
          },
          "email": {
            "type": [
              "string",
              "null"
            ],
            "example": "taller@ejemplo.com"
          },
          "web": {
            "type": [
              "string",
              "null"
            ],
            "example": "https://www.ejemplo.com"
          },
          "logo_url": {
            "type": [
              "string",
              "null"
            ],
            "example": null
          },
          "timezone": {
            "type": "string",
            "example": "Europe/Madrid"
          },
          "currency": {
            "type": "string",
            "enum": [
              "EUR"
            ],
            "example": "EUR"
          },
          "booking_mode": {
            "type": "string",
            "enum": [
              "propose",
              "book"
            ],
            "description": "propose: o cliente propõe e a oficina confirma; book: o cliente agenda diretamente",
            "example": "propose"
          },
          "schedule": {
            "type": "object",
            "properties": {
              "mon": {
                "$ref": "#/components/schemas/DaySchedule"
              },
              "tue": {
                "$ref": "#/components/schemas/DaySchedule"
              },
              "wed": {
                "$ref": "#/components/schemas/DaySchedule"
              },
              "thu": {
                "$ref": "#/components/schemas/DaySchedule"
              },
              "fri": {
                "$ref": "#/components/schemas/DaySchedule"
              },
              "sat": {
                "$ref": "#/components/schemas/DaySchedule"
              },
              "sun": {
                "$ref": "#/components/schemas/DaySchedule"
              }
            },
            "description": "Horário semanal da agenda",
            "required": [
              "mon",
              "tue",
              "wed",
              "thu",
              "fri",
              "sat",
              "sun"
            ],
            "additionalProperties": false
          },
          "lunch_break": {
            "oneOf": [
              {
                "type": "object",
                "properties": {
                  "start": {
                    "type": "string",
                    "example": "13:30"
                  },
                  "end": {
                    "type": "string",
                    "example": "15:00"
                  }
                },
                "required": [
                  "start",
                  "end"
                ],
                "additionalProperties": false
              },
              {
                "type": "null"
              }
            ]
          },
          "min_slot_minutes": {
            "type": "integer",
            "example": 60
          },
          "upcoming_holidays": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Holiday"
            },
            "description": "Feriados dos próximos 90 dias (nacionais, regionais e os da oficina)"
          }
        },
        "description": "Dados públicos da oficina",
        "required": [
          "name",
          "legal_name",
          "tax_id",
          "address",
          "city",
          "zip",
          "province",
          "phone",
          "whatsapp",
          "email",
          "web",
          "logo_url",
          "timezone",
          "currency",
          "booking_mode",
          "schedule",
          "lunch_break",
          "min_slot_minutes",
          "upcoming_holidays"
        ],
        "additionalProperties": false
      },
      "Communication": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "example": "c_8812"
          },
          "channel": {
            "type": "string",
            "enum": [
              "email",
              "sms",
              "whatsapp",
              "call",
              "push"
            ],
            "example": "email"
          },
          "direction": {
            "type": "string",
            "enum": [
              "in",
              "out"
            ],
            "example": "out"
          },
          "recipient": {
            "type": [
              "string",
              "null"
            ],
            "example": "laura@ejemplo.com"
          },
          "subject": {
            "type": [
              "string",
              "null"
            ],
            "example": "Seu orçamento"
          },
          "body": {
            "type": [
              "string",
              "null"
            ],
            "description": "Conteúdo completo",
            "example": "Olá, Laura, …"
          },
          "idlead": {
            "type": [
              "integer",
              "null"
            ],
            "example": 1234
          },
          "idclient": {
            "type": [
              "integer",
              "null"
            ],
            "example": 1204
          },
          "clientName": {
            "type": [
              "string",
              "null"
            ],
            "example": "Laura Gómez"
          },
          "status": {
            "type": "string",
            "example": "sent"
          },
          "created_at": {
            "type": "string",
            "example": "2026-10-06T09:30:00.000Z"
          },
          "duration": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Segundos (só ligações)",
            "example": null
          },
          "recordingUrl": {
            "type": [
              "string",
              "null"
            ],
            "example": null
          },
          "agent": {
            "type": [
              "string",
              "null"
            ],
            "example": null
          },
          "fromNumber": {
            "type": [
              "string",
              "null"
            ],
            "example": null
          },
          "toNumber": {
            "type": [
              "string",
              "null"
            ],
            "example": null
          }
        },
        "description": "Comunicação registrada",
        "required": [
          "id",
          "channel",
          "direction",
          "recipient",
          "subject",
          "body",
          "idlead",
          "idclient",
          "clientName",
          "status",
          "created_at",
          "duration",
          "recordingUrl",
          "agent",
          "fromNumber",
          "toNumber"
        ],
        "additionalProperties": false
      },
      "Ping": {
        "type": "object",
        "properties": {
          "ok": {
            "type": "boolean",
            "example": true
          },
          "key": {
            "type": "object",
            "properties": {
              "id": {
                "type": "integer",
                "example": 7
              },
              "name": {
                "type": "string",
                "example": "CRM"
              },
              "environment": {
                "type": "string",
                "enum": [
                  "live",
                  "test"
                ],
                "example": "live"
              },
              "scopes": {
                "type": "array",
                "items": {
                  "type": "string",
                  "example": "clients:read"
                }
              },
              "expires_at": {
                "type": [
                  "string",
                  "null"
                ],
                "format": "date-time",
                "example": null
              }
            },
            "required": [
              "id",
              "name",
              "environment",
              "scopes",
              "expires_at"
            ],
            "additionalProperties": false
          },
          "rate_limit": {
            "type": "object",
            "properties": {
              "per_minute": {
                "type": "object",
                "properties": {
                  "limit": {
                    "type": "integer",
                    "example": 60
                  },
                  "used": {
                    "type": "integer",
                    "example": 1
                  },
                  "remaining": {
                    "type": "integer",
                    "example": 59
                  }
                },
                "required": [
                  "limit",
                  "used",
                  "remaining"
                ],
                "additionalProperties": false
              },
              "per_day": {
                "type": "object",
                "properties": {
                  "limit": {
                    "type": "integer",
                    "example": 20000
                  },
                  "used": {
                    "type": "integer",
                    "example": 1
                  },
                  "remaining": {
                    "type": "integer",
                    "example": 19999
                  }
                },
                "required": [
                  "limit",
                  "used",
                  "remaining"
                ],
                "additionalProperties": false
              }
            },
            "required": [
              "per_minute",
              "per_day"
            ],
            "additionalProperties": false
          },
          "instance": {
            "type": "string",
            "description": "Identificador da instância (host)",
            "example": "taller.ejemplo.com"
          },
          "server_time": {
            "type": "string",
            "example": "2026-10-06T09:30:00.000Z"
          },
          "version": {
            "type": "string",
            "description": "Versão da API",
            "example": "v1"
          }
        },
        "description": "Status da chave",
        "required": [
          "ok",
          "key",
          "rate_limit",
          "instance",
          "server_time",
          "version"
        ],
        "additionalProperties": false
      },
      "Slot": {
        "type": "object",
        "properties": {
          "start": {
            "type": "string",
            "description": "Início (ISO 8601 UTC)",
            "example": "2026-10-14T07:00:00.000Z",
            "format": "date-time"
          },
          "end": {
            "type": "string",
            "example": "2026-10-14T08:00:00.000Z",
            "format": "date-time"
          },
          "box": {
            "$ref": "#/components/schemas/BoxRef"
          }
        },
        "description": "Horário livre",
        "required": [
          "start",
          "end",
          "box"
        ],
        "additionalProperties": false
      },
      "AppointmentAvailability": {
        "type": "object",
        "properties": {
          "data": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Slot"
            }
          },
          "duration_minutes": {
            "type": "integer",
            "example": 60
          },
          "from": {
            "type": "string",
            "example": "2026-10-14T00:00:00.000Z",
            "format": "date-time"
          },
          "to": {
            "type": "string",
            "example": "2026-10-21T00:00:00.000Z",
            "format": "date-time"
          },
          "booking_mode": {
            "type": "string",
            "enum": [
              "propose",
              "book"
            ],
            "example": "propose"
          }
        },
        "description": "Horários livres com a mesma lógica da agenda",
        "required": [
          "data",
          "duration_minutes",
          "from",
          "to",
          "booking_mode"
        ],
        "additionalProperties": false
      },
      "AppointmentCancelled": {
        "type": "object",
        "properties": {
          "ok": {
            "type": "boolean",
            "example": true
          },
          "id": {
            "type": "string",
            "example": "b1234"
          },
          "status": {
            "type": "string",
            "enum": [
              "cancelled",
              "withdrawn"
            ],
            "description": "cancelled: agendamento confirmado cancelado; withdrawn: proposta retirada",
            "example": "cancelled"
          }
        },
        "required": [
          "ok",
          "id",
          "status"
        ],
        "additionalProperties": false
      },
      "BudgetDocument": {
        "type": "object",
        "properties": {
          "id": {
            "type": "integer",
            "example": 991
          },
          "budget_id": {
            "type": "integer",
            "example": 1234
          },
          "filename": {
            "type": "string",
            "example": "parte-de-trabajo.pdf"
          },
          "content_type": {
            "type": "string",
            "enum": [
              "image/jpeg",
              "image/png",
              "image/webp",
              "application/pdf"
            ],
            "example": "application/pdf"
          },
          "size": {
            "type": "integer",
            "description": "Bytes",
            "example": 182044
          },
          "url": {
            "type": "string",
            "example": "https://…/leads/1234/api-parte-de-trabajo.pdf"
          },
          "client_visible": {
            "type": "boolean",
            "description": "Visível para o cliente no link de acompanhamento",
            "example": false
          },
          "mechanic_visible": {
            "type": "boolean",
            "description": "Visível no app do mecânico",
            "example": false
          },
          "created_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "example": "2026-10-06T09:30:00.000Z"
          }
        },
        "description": "Documento anexado a um orçamento",
        "required": [
          "id",
          "budget_id",
          "filename",
          "content_type",
          "size",
          "url",
          "client_visible",
          "mechanic_visible",
          "created_at"
        ],
        "additionalProperties": false
      },
      "WebhookEnvelope": {
        "type": "object",
        "properties": {
          "event": {
            "type": "string",
            "description": "Nome do evento",
            "example": "lead.accepted"
          },
          "timestamp": {
            "type": "string",
            "example": "2026-10-06T10:15:00.000Z"
          },
          "data": {
            "type": "object",
            "description": "Carga específica do evento",
            "additionalProperties": true,
            "example": {
              "leadId": 1234,
              "from": "Enviado",
              "to": "Aprobado"
            }
          }
        },
        "description": "Corpo de toda entrega de webhook",
        "required": [
          "event",
          "timestamp",
          "data"
        ],
        "additionalProperties": false
      },
      "Webhook": {
        "type": "object",
        "properties": {
          "id": {
            "type": "integer",
            "example": 3
          },
          "url": {
            "type": "string",
            "example": "https://tu-sistema.com/webhooks/taller"
          },
          "events": {
            "type": "array",
            "items": {
              "type": "string",
              "example": "lead.accepted"
            }
          },
          "description": {
            "type": [
              "string",
              "null"
            ],
            "example": "CRM"
          },
          "active": {
            "type": "boolean",
            "example": true
          },
          "created_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "example": "2026-10-06T09:30:00.000Z"
          }
        },
        "description": "Assinatura de eventos (nunca inclui o segredo)",
        "required": [
          "id",
          "url",
          "events",
          "description",
          "active",
          "created_at"
        ],
        "additionalProperties": false
      }
    },
    "x-scopes": [
      {
        "scope": "clients:read",
        "area": "clients",
        "label": "Ler clientes",
        "description": "Consultar cadastros de clientes e seus dados de contato.",
        "sideEffect": false
      },
      {
        "scope": "clients:write",
        "area": "clients",
        "label": "Criar e editar clientes",
        "description": "Cadastrar clientes novos e alterar os existentes.",
        "sideEffect": true
      },
      {
        "scope": "vehicles:read",
        "area": "vehicles",
        "label": "Ler veículos",
        "description": "Consultar veículos, placas e seu histórico.",
        "sideEffect": false
      },
      {
        "scope": "vehicles:write",
        "area": "vehicles",
        "label": "Criar e editar veículos",
        "description": "Cadastrar veículos e alterar seus dados.",
        "sideEffect": true
      },
      {
        "scope": "budgets:read",
        "area": "budgets",
        "label": "Ler orçamentos",
        "description": "Consultar orçamentos, seus itens e status.",
        "sideEffect": false
      },
      {
        "scope": "budgets:write",
        "area": "budgets",
        "label": "Criar e editar orçamentos",
        "description": "Criar orçamentos, adicionar itens e mudar o status.",
        "sideEffect": true
      },
      {
        "scope": "invoices:read",
        "area": "invoices",
        "label": "Ler faturas",
        "description": "Consultar faturas emitidas, valores e recebimentos. Emitir faturas não está disponível pela API.",
        "sideEffect": false
      },
      {
        "scope": "appointments:read",
        "area": "appointments",
        "label": "Ler agendamentos",
        "description": "Consultar a agenda e os horários disponíveis.",
        "sideEffect": false
      },
      {
        "scope": "appointments:write",
        "area": "appointments",
        "label": "Agendar e cancelar",
        "description": "Criar, mover e cancelar agendamentos na agenda.",
        "sideEffect": true
      },
      {
        "scope": "communications:read",
        "area": "communications",
        "label": "Ler comunicações",
        "description": "Consultar o registro de e-mails, SMS, WhatsApp e ligações (inclui o conteúdo completo).",
        "sideEffect": false
      },
      {
        "scope": "communications:send",
        "area": "communications",
        "label": "Enviar e-mail e SMS",
        "description": "Enviar e-mails e SMS transacionais pela instância; consome saldo.",
        "sideEffect": true
      },
      {
        "scope": "catalog:read",
        "area": "catalog",
        "label": "Ler catálogo",
        "description": "Consultar serviços, tarifas e itens da tabela de preços.",
        "sideEffect": false
      },
      {
        "scope": "stock:read",
        "area": "stock",
        "label": "Ler estoque",
        "description": "Consultar estoque e códigos de peças.",
        "sideEffect": false
      },
      {
        "scope": "stock:write",
        "area": "stock",
        "label": "Ajustar estoque",
        "description": "Lançar entradas e saídas de peças.",
        "sideEffect": true
      },
      {
        "scope": "webhooks:manage",
        "area": "webhooks",
        "label": "Gerenciar webhooks",
        "description": "Criar, listar e excluir as assinaturas de eventos desta chave.",
        "sideEffect": true
      },
      {
        "scope": "reports:read",
        "area": "reports",
        "label": "Ler relatórios",
        "description": "Consultar números agregados de faturamento, atividade e desempenho.",
        "sideEffect": false
      }
    ],
    "x-error-codes": [
      {
        "code": "missing_api_key",
        "status": 401,
        "message": "Falta a chave de API: envie no cabeçalho Authorization: Bearer pt_… ou X-Api-Key."
      },
      {
        "code": "invalid_api_key",
        "status": 401,
        "message": "A chave de API não é válida para esta instância."
      },
      {
        "code": "revoked_api_key",
        "status": 401,
        "message": "A chave de API foi revogada."
      },
      {
        "code": "expired_api_key",
        "status": 401,
        "message": "A chave de API expirou."
      },
      {
        "code": "ip_not_allowed",
        "status": 403,
        "message": "O endereço IP de origem não está na lista permitida desta chave."
      },
      {
        "code": "insufficient_scope",
        "status": 403,
        "message": "A chave de API não tem a permissão necessária para esta operação."
      },
      {
        "code": "test_key_forbidden",
        "status": 403,
        "message": "Uma chave de teste (pt_test_) não pode fazer operações com efeitos: use uma chave live."
      },
      {
        "code": "rate_limited",
        "status": 429,
        "message": "Você excedeu o limite de requisições desta chave. Aguarde e tente de novo."
      },
      {
        "code": "instance_rate_limited",
        "status": 503,
        "message": "A instância está recebendo requisições demais pela API neste momento. Tente de novo em alguns segundos."
      },
      {
        "code": "plan_quota_exceeded",
        "status": 429,
        "message": "A cota diária de requisições do plano de API da oficina acabou. Ela renova às 00:00 UTC; para mais volume, mude de plano."
      },
      {
        "code": "plan_scope_not_allowed",
        "status": 403,
        "message": "O plano de API da oficina não inclui esta permissão. Para usá-la é preciso mudar de plano."
      },
      {
        "code": "bad_request",
        "status": 400,
        "message": "A requisição não é válida."
      },
      {
        "code": "not_found",
        "status": 404,
        "message": "Recurso não encontrado."
      },
      {
        "code": "upstream_error",
        "status": 502,
        "message": "Um serviço externo recusou a operação."
      },
      {
        "code": "internal_error",
        "status": 500,
        "message": "Erro interno."
      },
      {
        "code": "validation_error",
        "status": 422,
        "message": "O corpo da requisição não é válido: confira a lista «fields»."
      },
      {
        "code": "idempotency_key_required",
        "status": 400,
        "message": "Falta o cabeçalho Idempotency-Key (obrigatório em todo POST; de 8 a 255 caracteres visíveis)."
      },
      {
        "code": "idempotency_conflict",
        "status": 409,
        "message": "Essa Idempotency-Key já foi usada nas últimas 24 h com outra requisição."
      },
      {
        "code": "idempotency_in_progress",
        "status": 409,
        "message": "Há outra requisição com a mesma Idempotency-Key em andamento. Tente de novo em alguns segundos."
      },
      {
        "code": "client_exists",
        "status": 409,
        "message": "Já existe um cliente com esse telefone, e-mail ou NIF/CIF."
      },
      {
        "code": "client_erased",
        "status": 409,
        "message": "O cliente pediu a exclusão dos seus dados (RGPD): o cadastro não aceita alterações nem registros vinculados."
      },
      {
        "code": "vehicle_exists",
        "status": 409,
        "message": "Esse cliente já tem um veículo com essa placa."
      },
      {
        "code": "vehicle_belongs_to_other_client",
        "status": 409,
        "message": "Essa placa já está cadastrada em nome de outro cliente."
      },
      {
        "code": "budget_locked",
        "status": 409,
        "message": "O orçamento está fechado e não aceita mais essa alteração."
      },
      {
        "code": "invalid_status_transition",
        "status": 409,
        "message": "O orçamento não pode passar para esse status a partir do atual."
      },
      {
        "code": "status_transition_forbidden",
        "status": 403,
        "message": "Essa mudança de status não está disponível pela API."
      },
      {
        "code": "client_acceptance_required",
        "status": 403,
        "message": "A aprovação do orçamento precisa ser feita pelo cliente no link de acompanhamento assinado."
      },
      {
        "code": "booking_mode_propose_only",
        "status": 403,
        "message": "A oficina trabalha no modo «propor agendamento»: só é possível criar propostas que a oficina confirma."
      },
      {
        "code": "slot_unavailable",
        "status": 409,
        "message": "Esse horário não está disponível."
      },
      {
        "code": "payload_too_large",
        "status": 413,
        "message": "O arquivo excede o tamanho máximo (4 MB)."
      },
      {
        "code": "unsupported_media_type",
        "status": 415,
        "message": "Tipo de arquivo não aceito: só JPEG, PNG, WebP ou PDF."
      }
    ]
  },
  "security": [
    {
      "bearerAuth": []
    },
    {
      "apiKeyAuth": []
    }
  ]
}
