{
  "openapi": "3.1.0",
  "info": {
    "title": "API pública de Programa Taller",
    "version": "1.0.0",
    "description": "API REST de la instància del taller. Autenticació per clau (Bearer o X-Api-Key), respostes JSON, paginació per cursor, dates ISO 8601 en UTC i imports en euros amb dos decimals.\n\nClaus de client: el taller pot crear claus limitades a un únic client (per exemple, una flota o una empresa). Amb elles cada llistat retorna només les dades d'aquest client, un id d'un altre client respon 404, les operacions de tot el taller responen 403 client_key_forbidden i els webhooks només reben esdeveniments d'aquest client. GET /ping indica a key.client a quin client està limitada.",
    "x-languages": [
      "es",
      "en",
      "ca",
      "pt",
      "fr",
      "bg"
    ],
    "x-sandbox": {
      "url": "https://demo.programa-taller.es",
      "signup": "https://demo.programa-taller.es/desarrolladores-sandbox",
      "resetEveryHours": 2,
      "keyDays": 30
    },
    "x-guides": [
      {
        "id": "getting-started",
        "title": "Primers passos",
        "intro": "El mateix recorregut que el tutorial «Comença amb l'API pas a pas» de Desenvolupadors, per a qui programa. Crea una clau de proves (pt_test_…) amb permisos de només lectura, comprova la connexió, llegeix dades reals i, quan la integració estigui a punt, passa a una clau live amb els permisos justos. Els avisos automàtics (webhooks) i les escriptures requereixen el pla Developer o superior.",
        "steps": [
          {
            "title": "1. Crea una clau a «Les meves claus»",
            "operationId": null,
            "method": null,
            "path": null,
            "body": null,
            "note": "Tria l'entorn de proves i només permisos de lectura (per exemple budgets:read). La clau completa es mostra una sola vegada: desa-la en un gestor de contrasenyes i envia-la a la capçalera Authorization: Bearer."
          },
          {
            "title": "2. Comprova la connexió",
            "operationId": "ping",
            "method": "GET",
            "path": "/api/v1/ping",
            "body": null,
            "note": "Respon amb el nom de la clau, els seus permisos i l'entorn. Un 401 vol dir clau no vàlida; un 403, que falta un permís."
          },
          {
            "title": "3. Llegeix les teves dades reals",
            "operationId": "listBudgets",
            "method": "GET",
            "path": "/api/v1/budgets",
            "body": "?limit=3",
            "note": "Els llistats es paginen per cursor: repeteix la crida amb cursor=<next_cursor> fins que has_more sigui false. Cada resposta porta X-RateLimit-Remaining amb les peticions que queden a la finestra."
          },
          {
            "title": "4. Rep avisos automàtics",
            "operationId": "createWebhook",
            "method": "POST",
            "path": "/api/v1/webhooks",
            "body": "{ \"url\": \"https://el-teu-sistema.com/avisos-del-taller\", \"events\": [\"lead.accepted\", \"vehicle.ready\"] }",
            "note": "També es creen des de la pestanya Webhooks sense programar. Desa el secret de signatura i verifica X-PT-Signature a cada lliurament; respon 2xx en menys de 5 segons."
          },
          {
            "title": "5. Passa a producció",
            "operationId": null,
            "method": null,
            "path": null,
            "body": null,
            "note": "Abans, si vols, prova escriptures, acceptacions, factures i missatges sense risc a l'entorn de proves (https://demo.programa-taller.es/desarrolladores-sandbox): dades fictícies i res no surt de debò. Crea una clau live amb els permisos mínims i, si vas a escriure dades, afegeix Idempotency-Key a cada POST. Els límits i el pla d'API del taller es veuen a la pestanya Consum; els plans Developer i Business es contracten des del mateix programa."
          }
        ]
      },
      {
        "id": "budget-and-appointment",
        "title": "Guia: crear un pressupost i proposar cita",
        "intro": "Flux típic d'un CRM o d'una web de reserves. Totes les peticions POST porten Idempotency-Key (un UUID nou per operació; repeteix-lo només quan reintentis la mateixa). Necessites una clau live amb clients:write, vehicles:write, budgets:write, appointments:read i appointments:write.",
        "steps": [
          {
            "title": "1. Client: crear-lo o recuperar l'existent",
            "operationId": "createClient",
            "method": "POST",
            "path": "/api/v1/clients",
            "body": "{ \"name\": \"Laura Gómez\", \"phone\": \"600111222\", \"email\": \"laura@ejemplo.com\" }",
            "note": "Amb ?on_conflict=return_existing, si el telèfon, l'email o el NIF ja existeixen reps aquest client (200) en lloc de 409."
          },
          {
            "title": "2. Vehicle del client",
            "operationId": "createVehicle",
            "method": "POST",
            "path": "/api/v1/vehicles",
            "body": "{ \"client_id\": 1204, \"plate\": \"1234KLM\", \"brand\": \"Seat\", \"model\": \"León\" }",
            "note": "Si la matrícula és d'un altre client: 409 vehicle_belongs_to_other_client (decideix el taller)."
          },
          {
            "title": "3. Pressupost amb les seves partides",
            "operationId": "createBudget",
            "method": "POST",
            "path": "/api/v1/budgets",
            "body": "{ \"client_id\": 1204, \"vehicle_id\": 871, \"lines\": [{ \"description\": \"Canvi d'oli i filtre\", \"quantity\": 1, \"unit_price\": 65 }] }",
            "note": "La resposta porta els totals calculats. El client accepta i signa des de l'enllaç que li envia el taller (l'API no dona mai aquest enllaç); si t'ho confirma a tu per una altra via, accepta'l en nom seu amb POST /budgets/{id}/accept (permís budgets:accept)."
          },
          {
            "title": "4. Forats lliures",
            "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. Proposar la cita",
            "operationId": "createAppointment",
            "method": "POST",
            "path": "/api/v1/appointments",
            "body": "{ \"budget_id\": 1234, \"start\": \"2026-10-14T09:00:00+02:00\", \"duration_minutes\": 60 }",
            "note": "En mode «proposar» queda status=proposed fins que el taller la confirma (rebràs appointment.confirmed per webhook). Si el forat s'ha ocupat: 409 slot_unavailable amb alternatives."
          }
        ]
      },
      {
        "id": "external-bot",
        "title": "Guia: el teu bot o IA als canals del taller",
        "intro": "El teu assistent contesta per WhatsApp, SMS, xat web o correu en lloc de l'assistent del programa. Necessites una clau live amb communications:read, messages:send, settings:write i webhooks:manage (pla Business o Partner). Prova-ho abans a l'entorn de proves amb POST /sandbox/simulate/message.",
        "steps": [
          {
            "title": "1. Subscriu-te a message.received",
            "operationId": "createWebhook",
            "method": "POST",
            "path": "/api/v1/webhooks",
            "body": "{ \"url\": \"https://el-teu-bot.com/taller\", \"events\": [\"message.received\"] }",
            "note": "Cada missatge del client arriba signat amb la clau de la conversa (key), el client, el pressupost actiu, el text i els adjunts."
          },
          {
            "title": "2. Activa el mode assistent extern",
            "operationId": "updateAssistantSettings",
            "method": "PUT",
            "path": "/api/v1/settings/assistant",
            "body": "{ \"external_channels\": [\"whatsapp\", \"chat\"], \"fallback_minutes\": 10 }",
            "note": "En aquests canals l'assistent del programa deixa de contestar. Si no respons a temps, o la teva clau o el teu webhook deixen de funcionar, torna a contestar ell a aquest missatge."
          },
          {
            "title": "3. Llegeix el fil si ho necessites",
            "operationId": "listConversationMessages",
            "method": "GET",
            "path": "/api/v1/conversations/{key}/messages",
            "body": null,
            "note": null
          },
          {
            "title": "4. Respon",
            "operationId": "sendConversationMessage",
            "method": "POST",
            "path": "/api/v1/conversations/{key}/messages",
            "body": "{ \"text\": \"Demà a les 9:00 tenim lloc. Te la reservo?\" }",
            "note": "Fora de la finestra de 24 h de WhatsApp envia template_id (GET /messages/templates). Per tancar la venda: POST /appointments, POST /budgets/{id}/accept o POST /budgets/{id}/invoice segons els teus permisos."
          }
        ]
      },
      {
        "id": "fleet-client-key",
        "title": "Guia: flotes i rènting amb una clau de client",
        "intro": "Perquè una flota, una empresa de rènting o un client amb molts vehicles connecti els seus sistemes amb el taller sense veure les dades dels altres clients, el taller li crea una clau limitada a la seva fitxa de client («Limitar a un client» en crear-la a Les meves claus). Amb ella cada llistat retorna només les seves dades, un id d'un altre client respon 404, les operacions de tot el taller responen 403 client_key_forbidden i els seus webhooks només reben els seus esdeveniments.",
        "steps": [
          {
            "title": "1. Comprova a quin client està limitada",
            "operationId": "ping",
            "method": "GET",
            "path": "/api/v1/ping",
            "body": null,
            "note": "key.client porta l'id i el nom del client; en una clau del taller és null."
          },
          {
            "title": "2. Els seus vehicles",
            "operationId": "listVehicles",
            "method": "GET",
            "path": "/api/v1/vehicles",
            "body": "?limit=200",
            "note": "Només surten els vehicles de la seva fitxa. Per donar-ne d'alta un de nou, POST /vehicles amb el seu propi client_id."
          },
          {
            "title": "3. Demanar un pressupost",
            "operationId": "createBudget",
            "method": "POST",
            "path": "/api/v1/budgets",
            "body": "{ \"client_id\": 1204, \"vehicle_id\": 871, \"public_notes\": \"Revisió dels 60.000 km\" }",
            "note": "client_id ha de ser el de la clau (si no, 403 client_key_forbidden). El taller el completa i l'envia; la flota l'accepta o el rebutja amb POST /budgets/{id}/accept o /reject (permís budgets:accept)."
          },
          {
            "title": "4. Proposar cita i seguir l'estat",
            "operationId": "createAppointment",
            "method": "POST",
            "path": "/api/v1/appointments",
            "body": "{ \"budget_id\": 1234, \"start\": \"2026-10-20T09:00:00+02:00\", \"duration_minutes\": 60 }",
            "note": "Subscriu un webhook a lead.status_changed, vehicle.ready i invoice.issued per assabentar-te'n al moment; només arribaran els dels seus vehicles."
          }
        ]
      },
      {
        "id": "sandbox",
        "title": "Entorn de proves",
        "intro": "https://demo.programa-taller.es és una instància amb dades fictícies que es restauren cada 2 hores. Qualsevol clau d'allà és de proves amb tots els permisos: pot escriure, acceptar, facturar i «enviar», però res no surt de debò (els enviaments es registren com a simulats) i els webhooks s'emeten igualment per provar el cicle complet. Les teves claus, webhooks i registre de peticions es conserven entre reinicis.",
        "steps": [
          {
            "title": "1. Demana la teva clau",
            "operationId": "createSandboxKey",
            "method": "POST",
            "path": "/api/v1/sandbox/keys",
            "body": "{ \"email\": \"dev@exemple.com\", \"company\": \"Integracions Exemple SL\", \"name\": \"Anna Puig\" }",
            "note": "També des de la pàgina https://demo.programa-taller.es/desarrolladores-sandbox. La clau es mostra una sola vegada i val 30 dies."
          },
          {
            "title": "2. Simula un client que escriu",
            "operationId": "simulateInboundMessage",
            "method": "POST",
            "path": "/api/v1/sandbox/simulate/message",
            "body": "{ \"channel\": \"whatsapp\", \"from\": \"+34600111222\", \"text\": \"Teniu lloc demà?\" }",
            "note": null
          },
          {
            "title": "3. Canvia a producció",
            "operationId": null,
            "method": null,
            "path": null,
            "body": null,
            "note": "Canvia la URL base per la de la instància del taller i fes servir una clau live creada a la seva secció Desenvolupadors."
          }
        ]
      }
    ],
    "x-client-keys": {
      "forbiddenScopes": [
        "invoices:write",
        "stock:write",
        "settings:write",
        "reports:read"
      ],
      "description": "Claus de client: el taller pot crear claus limitades a un únic client (per exemple, una flota o una empresa). Amb elles cada llistat retorna només les dades d'aquest client, un id d'un altre client respon 404, les operacions de tot el taller responen 403 client_key_forbidden i els webhooks només reben esdeveniments d'aquest client. GET /ping indica a key.client a quin client està limitada."
    }
  },
  "servers": [
    {
      "url": "https://el-teu-taller.example",
      "description": "Instància del taller (producció)"
    },
    {
      "url": "https://demo.programa-taller.es",
      "description": "Entorn de proves: dades fictícies que es restauren cada 2 hores, qualsevol clau és de proves amb tots els permisos i res no surt de debò. Demana la teva clau a https://demo.programa-taller.es/desarrolladores-sandbox"
    }
  ],
  "tags": [
    {
      "name": "General",
      "x-area": "general"
    },
    {
      "name": "Clients",
      "x-area": "clients"
    },
    {
      "name": "Vehicles",
      "x-area": "vehicles"
    },
    {
      "name": "Pressupostos",
      "x-area": "budgets"
    },
    {
      "name": "Factures",
      "x-area": "invoices"
    },
    {
      "name": "Cites",
      "x-area": "appointments"
    },
    {
      "name": "Catàleg",
      "x-area": "catalog"
    },
    {
      "name": "Recanvis",
      "x-area": "stock"
    },
    {
      "name": "Comunicacions",
      "x-area": "communications"
    },
    {
      "name": "Converses",
      "x-area": "messages"
    },
    {
      "name": "Webhooks",
      "x-area": "webhooks"
    },
    {
      "name": "Ajustos",
      "x-area": "settings"
    },
    {
      "name": "Entorn de proves",
      "x-area": "sandbox"
    }
  ],
  "paths": {
    "/api/v1/ping": {
      "get": {
        "operationId": "ping",
        "tags": [
          "General"
        ],
        "summary": "Provar la connexió",
        "description": "Retorna el nom de la clau, l'entorn, els permisos, l'estat dels límits i, si és una clau de client, el client al qual està limitada (key.client; null en una clau del taller). Serveix qualsevol clau vàlida.\n\nQualsevol clau vàlida, sense cap permís concret.\n\nClau de client: permès (no porta dades d'altres clients).",
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Resposta correcta",
            "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": "Error — bad_request: La petició no és vàlida.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Error — missing_api_key: Falta la clau API: envia-la a la capçalera Authorization: Bearer pt_… o X-Api-Key. | invalid_api_key: La clau API no és vàlida per a aquesta instància.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Error — ip_not_allowed: L'adreça IP d'origen no és a la llista permesa d'aquesta clau.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Error — rate_limited: Has superat el límit de peticions d'aquesta clau. Espera i torna-ho a provar. | plan_quota_exceeded: S'ha esgotat la quota diària de peticions del pla d'API del taller. Es renova a les 00:00 UTC; per a més volum, millora el pla.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Error — internal_error: Error intern.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Error — instance_rate_limited: La instància està rebent massa peticions per API en aquest moment. Torna-ho a provar d'aquí a uns segons.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-client-key": "open"
      }
    },
    "/api/v1/workshop": {
      "get": {
        "operationId": "getWorkshop",
        "tags": [
          "General"
        ],
        "summary": "Dades públiques del taller",
        "description": "Nom, raó social, CIF, adreça, contacte, horari setmanal de l'agenda, mode de reserva de cites, festius propers i zona horària.\n\nQualsevol clau vàlida, sense cap permís concret.\n\nClau de client: permès (no porta dades d'altres clients).",
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Resposta correcta",
            "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": "Error — bad_request: La petició no és vàlida.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Error — missing_api_key: Falta la clau API: envia-la a la capçalera Authorization: Bearer pt_… o X-Api-Key. | invalid_api_key: La clau API no és vàlida per a aquesta instància.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Error — ip_not_allowed: L'adreça IP d'origen no és a la llista permesa d'aquesta clau.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Error — rate_limited: Has superat el límit de peticions d'aquesta clau. Espera i torna-ho a provar. | plan_quota_exceeded: S'ha esgotat la quota diària de peticions del pla d'API del taller. Es renova a les 00:00 UTC; per a més volum, millora el pla.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Error — internal_error: Error intern.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Error — instance_rate_limited: La instància està rebent massa peticions per API en aquest moment. Torna-ho a provar d'aquí a uns segons.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-client-key": "open"
      }
    },
    "/api/v1/openapi.json": {
      "get": {
        "operationId": "getOpenApi",
        "tags": [
          "General"
        ],
        "summary": "Especificació OpenAPI 3.1",
        "description": "Fitxer generat des d'aquest mateix catàleg. Sense autenticació. Admet ?lang=es|en|ca|pt|fr|bg per als textos.\n\nSense autenticació.",
        "security": [],
        "parameters": [
          {
            "name": "lang",
            "in": "query",
            "required": false,
            "description": "Idioma de les descripcions.",
            "schema": {
              "type": "string",
              "enum": [
                "es",
                "en",
                "ca",
                "pt",
                "fr",
                "bg"
              ],
              "default": "es"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Resposta correcta",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true,
                  "description": "Document OpenAPI 3.1"
                }
              }
            }
          },
          "400": {
            "description": "Error — bad_request: La petició no és vàlida.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Error — rate_limited: Has superat el límit de peticions d'aquesta clau. Espera i torna-ho a provar.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Error — internal_error: Error intern.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Error — instance_rate_limited: La instància està rebent massa peticions per API en aquest moment. Torna-ho a provar d'aquí a uns segons.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/clients": {
      "get": {
        "operationId": "listClients",
        "tags": [
          "Clients"
        ],
        "summary": "Llistar clients",
        "description": "Ordenats per id ascendent. Els clients esborrats per RGPD apareixen anonimitzats, amb erased_at informat.\n\nPermís necessari: `clients:read`.\n\nClau de client: només retorna o crea dades del seu client.",
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ],
        "x-scope": "clients:read",
        "parameters": [
          {
            "name": "search",
            "in": "query",
            "required": false,
            "description": "Cerca al nom, telèfon, email i NIF/CIF (mínim 2 caràcters).",
            "schema": {
              "type": "string",
              "example": "laura"
            }
          },
          {
            "name": "expand",
            "in": "query",
            "required": false,
            "description": "Relacions opcionals que cal incloure.",
            "schema": {
              "type": "string",
              "enum": [
                "vehicles"
              ]
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Resultats per pàgina (1–200).",
            "schema": {
              "type": "integer",
              "default": 50,
              "example": 50
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "required": false,
            "description": "Cursor opac retornat a next_cursor de la pàgina anterior.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Resposta correcta",
            "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 opac per demanar la pàgina següent; null si no n'hi ha més",
                      "example": "aWQ6MTIzNA"
                    },
                    "has_more": {
                      "type": "boolean",
                      "description": "true si queden més resultats",
                      "example": true
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Error — bad_request: La petició no és vàlida.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Error — missing_api_key: Falta la clau API: envia-la a la capçalera Authorization: Bearer pt_… o X-Api-Key. | invalid_api_key: La clau API no és vàlida per a aquesta instància.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Error — ip_not_allowed: L'adreça IP d'origen no és a la llista permesa d'aquesta clau. | insufficient_scope: La clau API no té el permís necessari per a aquesta operació. | plan_scope_not_allowed: El pla d'API del taller no inclou aquest permís. Per fer-lo servir cal millorar el pla.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Error — rate_limited: Has superat el límit de peticions d'aquesta clau. Espera i torna-ho a provar. | plan_quota_exceeded: S'ha esgotat la quota diària de peticions del pla d'API del taller. Es renova a les 00:00 UTC; per a més volum, millora el pla.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Error — internal_error: Error intern.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Error — instance_rate_limited: La instància està rebent massa peticions per API en aquest moment. Torna-ho a provar d'aquí a uns segons.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-client-key": "filtered"
      },
      "post": {
        "operationId": "createClient",
        "tags": [
          "Clients"
        ],
        "summary": "Crear un client",
        "description": "Mai no fusiona amb una fitxa existent: si el NIF/CIF, l'email o el telèfon ja són en un altre client respon 409 client_exists amb existing_id (o 200 amb aquest client si passes ?on_conflict=return_existing). El telèfon es desa com a la fitxa (Espanya en 9 dígits, altres països amb prefix) i l'email en minúscules; no es corregeixen errades. Les baixes comercials queden al registre de consentiments.\n\nPermís necessari: `clients:write`.\n\nClau de client: no disponible (403 client_key_forbidden).",
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ],
        "x-scope": "clients:write",
        "parameters": [
          {
            "name": "on_conflict",
            "in": "query",
            "required": false,
            "description": "error (per defecte): 409 si ja existeix. return_existing: 200 amb el client existent.",
            "schema": {
              "type": "string",
              "enum": [
                "error",
                "return_existing"
              ],
              "default": "error"
            }
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": true,
            "description": "Clau única per operació (es recomana un UUID). Repetir-la amb el mateix cos en 24 h retorna la resposta desada amb Idempotent-Replay: true; amb un altre cos, 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": "Nom i cognoms o raó social",
                    "example": "Laura Gómez"
                  },
                  "phone": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 30,
                    "description": "Telèfon principal. Espanya en 9 dígits o amb +34; altres països amb el seu prefix (+351…)",
                    "example": "600111222",
                    "pattern": "^[0-9+()\\s.\\-]{6,30}$"
                  },
                  "phone_secondary": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 30,
                    "description": "Segon telèfon",
                    "example": null,
                    "pattern": "^[0-9+()\\s.\\-]{6,30}$"
                  },
                  "email": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 254,
                    "description": "Email (no es corregeix: se'n valida el format)",
                    "example": "laura@ejemplo.com",
                    "format": "email"
                  },
                  "billing_email": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 254,
                    "description": "Email per a les factures, si és un altre",
                    "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 de les comunicacions",
                    "example": "es"
                  },
                  "preferred_contact_method": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "enum": [
                      "email",
                      "sms",
                      "whatsapp",
                      "phone",
                      null
                    ],
                    "description": "Canal preferit",
                    "example": "whatsapp"
                  },
                  "marketing_opt_out": {
                    "type": "boolean",
                    "description": "true: no vol comunicacions comercials. Queda al registre de consentiments",
                    "example": false
                  },
                  "channel_opt_out": {
                    "type": "object",
                    "properties": {
                      "email": {
                        "type": "boolean",
                        "description": "Sense publicitat per email",
                        "example": false
                      },
                      "sms": {
                        "type": "boolean",
                        "description": "Sense publicitat per SMS",
                        "example": false
                      },
                      "whatsapp": {
                        "type": "boolean",
                        "description": "Sense publicitat per WhatsApp",
                        "example": false
                      },
                      "call": {
                        "type": "boolean",
                        "description": "Sense trucades comercials",
                        "example": false
                      }
                    },
                    "required": [],
                    "additionalProperties": false,
                    "description": "Baixes comercials per canal (els avisos de servei no canvien)"
                  }
                },
                "required": [
                  "name"
                ],
                "additionalProperties": false,
                "description": "Alta de client"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Resposta correcta",
            "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": "Error — bad_request: La petició no és vàlida. | idempotency_key_required: Falta la capçalera Idempotency-Key (obligatòria a tot POST; de 8 a 255 caràcters visibles).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Error — missing_api_key: Falta la clau API: envia-la a la capçalera Authorization: Bearer pt_… o X-Api-Key. | invalid_api_key: La clau API no és vàlida per a aquesta instància.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Error — ip_not_allowed: L'adreça IP d'origen no és a la llista permesa d'aquesta clau. | insufficient_scope: La clau API no té el permís necessari per a aquesta operació. | plan_scope_not_allowed: El pla d'API del taller no inclou aquest permís. Per fer-lo servir cal millorar el pla. | test_key_forbidden: Una clau de prova (pt_test_) no pot fer operacions amb efectes: fes servir una clau live. | client_key_forbidden: Aquesta clau està limitada a un client del taller i no pot fer aquesta operació: només treballa amb les dades del seu client.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Error — client_exists: Ja existeix un client amb aquest telèfon, email o NIF/CIF. | idempotency_conflict: Aquesta Idempotency-Key ja s'ha fet servir les últimes 24 h amb una altra petició. | idempotency_in_progress: Hi ha una altra petició amb la mateixa Idempotency-Key en curs. Torna-ho a provar d'aquí a uns segons.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "Error — validation_error: El cos de la petició no és vàlid: revisa la llista «fields».",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ValidationError"
                }
              }
            }
          },
          "429": {
            "description": "Error — rate_limited: Has superat el límit de peticions d'aquesta clau. Espera i torna-ho a provar. | plan_quota_exceeded: S'ha esgotat la quota diària de peticions del pla d'API del taller. Es renova a les 00:00 UTC; per a més volum, millora el pla.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Error — internal_error: Error intern.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Error — instance_rate_limited: La instància està rebent massa peticions per API en aquest moment. Torna-ho a provar d'aquí a uns segons.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-client-key": "deny"
      }
    },
    "/api/v1/clients/{id}": {
      "get": {
        "operationId": "getClient",
        "tags": [
          "Clients"
        ],
        "summary": "Detall d'un client",
        "description": "Permís necessari: `clients:read`.\n\nClau de client: només si el recurs és del seu client; si no, 404.",
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ],
        "x-scope": "clients:read",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Id del client.",
            "schema": {
              "type": "integer",
              "example": 1234
            }
          },
          {
            "name": "expand",
            "in": "query",
            "required": false,
            "description": "Relacions opcionals que cal incloure.",
            "schema": {
              "type": "string",
              "enum": [
                "vehicles"
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Resposta correcta",
            "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": "Error — bad_request: La petició no és vàlida.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Error — missing_api_key: Falta la clau API: envia-la a la capçalera Authorization: Bearer pt_… o X-Api-Key. | invalid_api_key: La clau API no és vàlida per a aquesta instància.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Error — ip_not_allowed: L'adreça IP d'origen no és a la llista permesa d'aquesta clau. | insufficient_scope: La clau API no té el permís necessari per a aquesta operació. | plan_scope_not_allowed: El pla d'API del taller no inclou aquest permís. Per fer-lo servir cal millorar el pla.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Error — not_found: No s'ha trobat el recurs.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Error — rate_limited: Has superat el límit de peticions d'aquesta clau. Espera i torna-ho a provar. | plan_quota_exceeded: S'ha esgotat la quota diària de peticions del pla d'API del taller. Es renova a les 00:00 UTC; per a més volum, millora el pla.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Error — internal_error: Error intern.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Error — instance_rate_limited: La instància està rebent massa peticions per API en aquest moment. Torna-ho a provar d'aquí a uns segons.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-client-key": "own:client"
      },
      "patch": {
        "operationId": "updateClient",
        "tags": [
          "Clients"
        ],
        "summary": "Modificar un client",
        "description": "Només canvien els camps enviats (null buida el camp). Es pot canviar l'email o el telèfon, i queda el valor anterior → nou a l'activitat del client amb el nom de la clau; emet client.updated. No es pot posar l'email, el telèfon o el NIF d'UNA ALTRA fitxa (409 client_exists). Les fitxes esborrades per RGPD responen 409 client_erased.\n\nPermís necessari: `clients:write`.\n\nClau de client: només si el recurs és del seu client; si no, 404.",
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ],
        "x-scope": "clients:write",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Id del client.",
            "schema": {
              "type": "integer",
              "example": 1234
            }
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Clau única per operació (es recomana un UUID). Repetir-la amb el mateix cos en 24 h retorna la resposta desada amb Idempotent-Replay: true; amb un altre cos, 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": "Nom i cognoms o raó social",
                    "example": "Laura Gómez Ruiz"
                  },
                  "phone": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 30,
                    "description": "Telèfon principal. Espanya en 9 dígits o amb +34; altres països amb el seu prefix (+351…)",
                    "example": "600111222",
                    "pattern": "^[0-9+()\\s.\\-]{6,30}$"
                  },
                  "phone_secondary": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 30,
                    "description": "Segon telèfon",
                    "example": null,
                    "pattern": "^[0-9+()\\s.\\-]{6,30}$"
                  },
                  "email": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 254,
                    "description": "Email (no es corregeix: se'n valida el format)",
                    "example": "laura@ejemplo.com",
                    "format": "email"
                  },
                  "billing_email": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 254,
                    "description": "Email per a les factures, si és un altre",
                    "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 de les comunicacions",
                    "example": "es"
                  },
                  "preferred_contact_method": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "enum": [
                      "email",
                      "sms",
                      "whatsapp",
                      "phone",
                      null
                    ],
                    "description": "Canal preferit",
                    "example": "whatsapp"
                  },
                  "marketing_opt_out": {
                    "type": "boolean",
                    "description": "true: no vol comunicacions comercials. Queda al registre de consentiments",
                    "example": false
                  },
                  "channel_opt_out": {
                    "type": "object",
                    "properties": {
                      "email": {
                        "type": "boolean",
                        "description": "Sense publicitat per email",
                        "example": false
                      },
                      "sms": {
                        "type": "boolean",
                        "description": "Sense publicitat per SMS",
                        "example": false
                      },
                      "whatsapp": {
                        "type": "boolean",
                        "description": "Sense publicitat per WhatsApp",
                        "example": false
                      },
                      "call": {
                        "type": "boolean",
                        "description": "Sense trucades comercials",
                        "example": false
                      }
                    },
                    "required": [],
                    "additionalProperties": false,
                    "description": "Baixes comercials per canal (els avisos de servei no canvien)"
                  }
                },
                "required": [],
                "additionalProperties": false,
                "description": "Canvis parcials: només els camps enviats. null buida el camp"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Resposta correcta",
            "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": "Error — bad_request: La petició no és vàlida.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Error — missing_api_key: Falta la clau API: envia-la a la capçalera Authorization: Bearer pt_… o X-Api-Key. | invalid_api_key: La clau API no és vàlida per a aquesta instància.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Error — ip_not_allowed: L'adreça IP d'origen no és a la llista permesa d'aquesta clau. | insufficient_scope: La clau API no té el permís necessari per a aquesta operació. | plan_scope_not_allowed: El pla d'API del taller no inclou aquest permís. Per fer-lo servir cal millorar el pla. | test_key_forbidden: Una clau de prova (pt_test_) no pot fer operacions amb efectes: fes servir una clau live.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Error — not_found: No s'ha trobat el recurs.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Error — client_exists: Ja existeix un client amb aquest telèfon, email o NIF/CIF. | client_erased: El client va demanar l'esborrat de les seves dades (RGPD): la fitxa no admet canvis ni altes associades.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "Error — validation_error: El cos de la petició no és vàlid: revisa la llista «fields».",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ValidationError"
                }
              }
            }
          },
          "429": {
            "description": "Error — rate_limited: Has superat el límit de peticions d'aquesta clau. Espera i torna-ho a provar. | plan_quota_exceeded: S'ha esgotat la quota diària de peticions del pla d'API del taller. Es renova a les 00:00 UTC; per a més volum, millora el pla.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Error — internal_error: Error intern.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Error — instance_rate_limited: La instància està rebent massa peticions per API en aquest moment. Torna-ho a provar d'aquí a uns segons.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-client-key": "own:client"
      }
    },
    "/api/v1/vehicles": {
      "get": {
        "operationId": "listVehicles",
        "tags": [
          "Vehicles"
        ],
        "summary": "Llistar vehicles",
        "description": "Inclou l'últim quilometratge anotat i el venciment de la ITV.\n\nPermís necessari: `vehicles:read`.\n\nClau de client: només retorna o crea dades del seu client.",
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ],
        "x-scope": "vehicles:read",
        "parameters": [
          {
            "name": "plate",
            "in": "query",
            "required": false,
            "description": "Matrícula exacta (s'ignoren espais i guions).",
            "schema": {
              "type": "string",
              "example": "1234KLM"
            }
          },
          {
            "name": "client_id",
            "in": "query",
            "required": false,
            "description": "Filtrar per client.",
            "schema": {
              "type": "integer",
              "example": 1204
            }
          },
          {
            "name": "status",
            "in": "query",
            "required": false,
            "description": "Estat del vehicle.",
            "schema": {
              "type": "string",
              "enum": [
                "activo",
                "baja_temporal",
                "baja"
              ]
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Resultats per pàgina (1–200).",
            "schema": {
              "type": "integer",
              "default": 50,
              "example": 50
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "required": false,
            "description": "Cursor opac retornat a next_cursor de la pàgina anterior.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Resposta correcta",
            "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 opac per demanar la pàgina següent; null si no n'hi ha més",
                      "example": "aWQ6MTIzNA"
                    },
                    "has_more": {
                      "type": "boolean",
                      "description": "true si queden més resultats",
                      "example": true
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Error — bad_request: La petició no és vàlida.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Error — missing_api_key: Falta la clau API: envia-la a la capçalera Authorization: Bearer pt_… o X-Api-Key. | invalid_api_key: La clau API no és vàlida per a aquesta instància.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Error — ip_not_allowed: L'adreça IP d'origen no és a la llista permesa d'aquesta clau. | insufficient_scope: La clau API no té el permís necessari per a aquesta operació. | plan_scope_not_allowed: El pla d'API del taller no inclou aquest permís. Per fer-lo servir cal millorar el pla.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Error — rate_limited: Has superat el límit de peticions d'aquesta clau. Espera i torna-ho a provar. | plan_quota_exceeded: S'ha esgotat la quota diària de peticions del pla d'API del taller. Es renova a les 00:00 UTC; per a més volum, millora el pla.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Error — internal_error: Error intern.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Error — instance_rate_limited: La instància està rebent massa peticions per API en aquest moment. Torna-ho a provar d'aquí a uns segons.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-client-key": "filtered"
      },
      "post": {
        "operationId": "createVehicle",
        "tags": [
          "Vehicles"
        ],
        "summary": "Donar d'alta un vehicle",
        "description": "Sempre a nom d'un client existent (client_id). La matrícula es normalitza (majúscules, sense espais ni guions). Si ja és a la fitxa d'UN ALTRE client: 409 vehicle_belongs_to_other_client. Si el mateix client ja la té: 409 vehicle_exists amb existing_id (o 200 amb ?on_conflict=return_existing).\n\nPermís necessari: `vehicles:write`.\n\nClau de client: només retorna o crea dades del seu client.",
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ],
        "x-scope": "vehicles:write",
        "parameters": [
          {
            "name": "on_conflict",
            "in": "query",
            "required": false,
            "description": "error (per defecte) o return_existing.",
            "schema": {
              "type": "string",
              "enum": [
                "error",
                "return_existing"
              ],
              "default": "error"
            }
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": true,
            "description": "Clau única per operació (es recomana un UUID). Repetir-la amb el mateix cos en 24 h retorna la resposta desada amb Idempotent-Replay: true; amb un altre cos, 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": "Client titular (ha d'existir)",
                    "example": 1204
                  },
                  "plate": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 15,
                    "description": "Matrícula; es desa en majúscules sense espais ni guions",
                    "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": "Etiqueta DGT (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": "Alta de vehicle d'un client"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Resposta correcta",
            "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": "Error — bad_request: La petició no és vàlida. | idempotency_key_required: Falta la capçalera Idempotency-Key (obligatòria a tot POST; de 8 a 255 caràcters visibles).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Error — missing_api_key: Falta la clau API: envia-la a la capçalera Authorization: Bearer pt_… o X-Api-Key. | invalid_api_key: La clau API no és vàlida per a aquesta instància.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Error — ip_not_allowed: L'adreça IP d'origen no és a la llista permesa d'aquesta clau. | insufficient_scope: La clau API no té el permís necessari per a aquesta operació. | plan_scope_not_allowed: El pla d'API del taller no inclou aquest permís. Per fer-lo servir cal millorar el pla. | test_key_forbidden: Una clau de prova (pt_test_) no pot fer operacions amb efectes: fes servir una clau live.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Error — vehicle_exists: Aquest client ja té un vehicle amb aquesta matrícula. | vehicle_belongs_to_other_client: Aquesta matrícula ja està donada d'alta a nom d'un altre client. | client_erased: El client va demanar l'esborrat de les seves dades (RGPD): la fitxa no admet canvis ni altes associades. | idempotency_conflict: Aquesta Idempotency-Key ja s'ha fet servir les últimes 24 h amb una altra petició. | idempotency_in_progress: Hi ha una altra petició amb la mateixa Idempotency-Key en curs. Torna-ho a provar d'aquí a uns segons.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "Error — validation_error: El cos de la petició no és vàlid: revisa la llista «fields».",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ValidationError"
                }
              }
            }
          },
          "429": {
            "description": "Error — rate_limited: Has superat el límit de peticions d'aquesta clau. Espera i torna-ho a provar. | plan_quota_exceeded: S'ha esgotat la quota diària de peticions del pla d'API del taller. Es renova a les 00:00 UTC; per a més volum, millora el pla.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Error — internal_error: Error intern.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Error — instance_rate_limited: La instància està rebent massa peticions per API en aquest moment. Torna-ho a provar d'aquí a uns segons.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-client-key": "filtered"
      }
    },
    "/api/v1/vehicles/{id}": {
      "get": {
        "operationId": "getVehicle",
        "tags": [
          "Vehicles"
        ],
        "summary": "Detall d'un vehicle",
        "description": "Permís necessari: `vehicles:read`.\n\nClau de client: només si el recurs és del seu client; si no, 404.",
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ],
        "x-scope": "vehicles:read",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Id del vehicle.",
            "schema": {
              "type": "integer",
              "example": 1234
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Resposta correcta",
            "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": "Error — bad_request: La petició no és vàlida.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Error — missing_api_key: Falta la clau API: envia-la a la capçalera Authorization: Bearer pt_… o X-Api-Key. | invalid_api_key: La clau API no és vàlida per a aquesta instància.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Error — ip_not_allowed: L'adreça IP d'origen no és a la llista permesa d'aquesta clau. | insufficient_scope: La clau API no té el permís necessari per a aquesta operació. | plan_scope_not_allowed: El pla d'API del taller no inclou aquest permís. Per fer-lo servir cal millorar el pla.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Error — not_found: No s'ha trobat el recurs.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Error — rate_limited: Has superat el límit de peticions d'aquesta clau. Espera i torna-ho a provar. | plan_quota_exceeded: S'ha esgotat la quota diària de peticions del pla d'API del taller. Es renova a les 00:00 UTC; per a més volum, millora el pla.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Error — internal_error: Error intern.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Error — instance_rate_limited: La instància està rebent massa peticions per API en aquest moment. Torna-ho a provar d'aquí a uns segons.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-client-key": "own:vehicle"
      },
      "patch": {
        "operationId": "updateVehicle",
        "tags": [
          "Vehicles"
        ],
        "summary": "Modificar un vehicle",
        "description": "Canvis parcials. client_id no admet null: un vehicle mai no es desvincula del titular per API (sí que pot passar a un altre client existent).\n\nPermís necessari: `vehicles:write`.\n\nClau de client: només si el recurs és del seu client; si no, 404.",
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ],
        "x-scope": "vehicles:write",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Id del vehicle.",
            "schema": {
              "type": "integer",
              "example": 1234
            }
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Clau única per operació (es recomana un UUID). Repetir-la amb el mateix cos en 24 h retorna la resposta desada amb Idempotent-Replay: true; amb un altre cos, 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": "Client titular (ha d'existir)",
                    "example": 1204
                  },
                  "plate": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 15,
                    "description": "Matrícula; es desa en majúscules sense espais ni guions",
                    "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": "Etiqueta DGT (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": "Canvis parcials del vehicle"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Resposta correcta",
            "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": "Error — bad_request: La petició no és vàlida.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Error — missing_api_key: Falta la clau API: envia-la a la capçalera Authorization: Bearer pt_… o X-Api-Key. | invalid_api_key: La clau API no és vàlida per a aquesta instància.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Error — ip_not_allowed: L'adreça IP d'origen no és a la llista permesa d'aquesta clau. | insufficient_scope: La clau API no té el permís necessari per a aquesta operació. | plan_scope_not_allowed: El pla d'API del taller no inclou aquest permís. Per fer-lo servir cal millorar el pla. | test_key_forbidden: Una clau de prova (pt_test_) no pot fer operacions amb efectes: fes servir una clau live.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Error — not_found: No s'ha trobat el recurs.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Error — vehicle_exists: Aquest client ja té un vehicle amb aquesta matrícula. | vehicle_belongs_to_other_client: Aquesta matrícula ja està donada d'alta a nom d'un altre client. | client_erased: El client va demanar l'esborrat de les seves dades (RGPD): la fitxa no admet canvis ni altes associades.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "Error — validation_error: El cos de la petició no és vàlid: revisa la llista «fields».",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ValidationError"
                }
              }
            }
          },
          "429": {
            "description": "Error — rate_limited: Has superat el límit de peticions d'aquesta clau. Espera i torna-ho a provar. | plan_quota_exceeded: S'ha esgotat la quota diària de peticions del pla d'API del taller. Es renova a les 00:00 UTC; per a més volum, millora el pla.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Error — internal_error: Error intern.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Error — instance_rate_limited: La instància està rebent massa peticions per API en aquest moment. Torna-ho a provar d'aquí a uns segons.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-client-key": "own:vehicle"
      }
    },
    "/api/v1/vehicles/{id}/history": {
      "get": {
        "operationId": "getVehicleHistory",
        "tags": [
          "Vehicles"
        ],
        "summary": "Historial del vehicle",
        "description": "Visites al taller (tots els seus pressupostos tret dels d'ús intern, del més recent al més antic) amb estat, entrada i sortida, quilometratge, import, núm. de factura i conceptes de les partides; més les intervencions anotades en altres tallers. No inclou mai costos.\n\nPermís necessari: `vehicles:read`.\n\nClau de client: només si el recurs és del seu client; si no, 404.",
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ],
        "x-scope": "vehicles:read",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Id del vehicle.",
            "schema": {
              "type": "integer",
              "example": 1234
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Màxim de visites (1–200).",
            "schema": {
              "type": "integer",
              "default": 50
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Resposta correcta",
            "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/VehicleHistory"
                }
              }
            }
          },
          "400": {
            "description": "Error — bad_request: La petició no és vàlida.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Error — missing_api_key: Falta la clau API: envia-la a la capçalera Authorization: Bearer pt_… o X-Api-Key. | invalid_api_key: La clau API no és vàlida per a aquesta instància.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Error — ip_not_allowed: L'adreça IP d'origen no és a la llista permesa d'aquesta clau. | insufficient_scope: La clau API no té el permís necessari per a aquesta operació. | plan_scope_not_allowed: El pla d'API del taller no inclou aquest permís. Per fer-lo servir cal millorar el pla.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Error — not_found: No s'ha trobat el recurs.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Error — rate_limited: Has superat el límit de peticions d'aquesta clau. Espera i torna-ho a provar. | plan_quota_exceeded: S'ha esgotat la quota diària de peticions del pla d'API del taller. Es renova a les 00:00 UTC; per a més volum, millora el pla.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Error — internal_error: Error intern.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Error — instance_rate_limited: La instància està rebent massa peticions per API en aquest moment. Torna-ho a provar d'aquí a uns segons.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-client-key": "own:vehicle"
      }
    },
    "/api/v1/budgets": {
      "get": {
        "operationId": "listBudgets",
        "tags": [
          "Pressupostos"
        ],
        "summary": "Llistar pressupostos",
        "description": "Mai no inclou els pressupostos d'ús intern del taller ni dades de cost. El detall (amb partides i totals) és a /budgets/{id}.\n\nPermís necessari: `budgets:read`.\n\nClau de client: només retorna o crea dades del seu client.",
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ],
        "x-scope": "budgets:read",
        "parameters": [
          {
            "name": "status",
            "in": "query",
            "required": false,
            "description": "Estat exacte, amb el valor en castellà (Pendiente, Enviado, Aprobado, Finalizado, Facturado, Rechazado…).",
            "schema": {
              "type": "string",
              "example": "Aprobado"
            }
          },
          {
            "name": "client_id",
            "in": "query",
            "required": false,
            "description": "Filtrar per client.",
            "schema": {
              "type": "integer",
              "example": 1204
            }
          },
          {
            "name": "vehicle_id",
            "in": "query",
            "required": false,
            "description": "Filtrar per vehicle.",
            "schema": {
              "type": "integer",
              "example": 871
            }
          },
          {
            "name": "from",
            "in": "query",
            "required": false,
            "description": "Creats a partir d'aquesta data.",
            "schema": {
              "type": "string",
              "format": "date-time",
              "example": "2026-10-01"
            }
          },
          {
            "name": "to",
            "in": "query",
            "required": false,
            "description": "Creats fins a aquesta data.",
            "schema": {
              "type": "string",
              "format": "date-time",
              "example": "2026-10-31"
            }
          },
          {
            "name": "updated_since",
            "in": "query",
            "required": false,
            "description": "Només registres modificats a partir d'aquesta data (ISO 8601).",
            "schema": {
              "type": "string",
              "format": "date-time",
              "example": "2026-10-01T00:00:00Z"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Resultats per pàgina (1–200).",
            "schema": {
              "type": "integer",
              "default": 50,
              "example": 50
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "required": false,
            "description": "Cursor opac retornat a next_cursor de la pàgina anterior.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Resposta correcta",
            "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 opac per demanar la pàgina següent; null si no n'hi ha més",
                      "example": "aWQ6MTIzNA"
                    },
                    "has_more": {
                      "type": "boolean",
                      "description": "true si queden més resultats",
                      "example": true
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Error — bad_request: La petició no és vàlida.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Error — missing_api_key: Falta la clau API: envia-la a la capçalera Authorization: Bearer pt_… o X-Api-Key. | invalid_api_key: La clau API no és vàlida per a aquesta instància.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Error — ip_not_allowed: L'adreça IP d'origen no és a la llista permesa d'aquesta clau. | insufficient_scope: La clau API no té el permís necessari per a aquesta operació. | plan_scope_not_allowed: El pla d'API del taller no inclou aquest permís. Per fer-lo servir cal millorar el pla.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Error — rate_limited: Has superat el límit de peticions d'aquesta clau. Espera i torna-ho a provar. | plan_quota_exceeded: S'ha esgotat la quota diària de peticions del pla d'API del taller. Es renova a les 00:00 UTC; per a més volum, millora el pla.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Error — internal_error: Error intern.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Error — instance_rate_limited: La instància està rebent massa peticions per API en aquest moment. Torna-ho a provar d'aquí a uns segons.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-client-key": "filtered"
      },
      "post": {
        "operationId": "createBudget",
        "tags": [
          "Pressupostos"
        ],
        "summary": "Crear un pressupost",
        "description": "Entra a «Pendiente» amb canal «API», igual que una alta des del programa (registre, fita d'obertura, webhook lead.created). Els totals es calculen al servidor amb l'impost del taller; si una partida no porta tax_rate, s'usa el del taller. El vehicle, si s'indica, ha de ser del client. No admet categories d'ús intern. Amb notify_client=true (i permís communications:send) s'envia al client l'acusament amb el seu enllaç de seguiment.\n\nPermís necessari: `budgets:write`.\n\nClau de client: només retorna o crea dades del seu client.",
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ],
        "x-scope": "budgets:write",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": true,
            "description": "Clau única per operació (es recomana un UUID). Repetir-la amb el mateix cos en 24 h retorna la resposta desada amb Idempotent-Replay: true; amb un altre cos, 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": "Client (ha d'existir)",
                    "example": 1204
                  },
                  "vehicle_id": {
                    "type": [
                      "integer",
                      "null"
                    ],
                    "minimum": 1,
                    "description": "Vehicle del client",
                    "example": 871
                  },
                  "category_id": {
                    "type": [
                      "integer",
                      "null"
                    ],
                    "minimum": 1,
                    "description": "Categoria de servei (GET /catalog/services)",
                    "example": 5
                  },
                  "subcategory_id": {
                    "type": [
                      "integer",
                      "null"
                    ],
                    "minimum": 1,
                    "description": "Subcategoria d'aquesta categoria",
                    "example": 51
                  },
                  "client_reference": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 100,
                    "description": "Referència del client (comanda, sinistre…) que veurà a la factura",
                    "example": "PED-2026-118"
                  },
                  "public_notes": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 5000,
                    "description": "Observacions visibles per al client",
                    "example": "Revisar també el soroll de la suspensió."
                  },
                  "lines": {
                    "type": "array",
                    "maxItems": 200,
                    "items": {
                      "type": "object",
                      "properties": {
                        "description": {
                          "type": "string",
                          "minLength": 1,
                          "maxLength": 2000,
                          "description": "Concepte visible per al client",
                          "example": "Canvi d'oli i filtre"
                        },
                        "quantity": {
                          "type": "number",
                          "minimum": 0,
                          "maximum": 100000,
                          "description": "Quantitat (hores en mà d'obra)",
                          "example": 1
                        },
                        "unit_price": {
                          "type": "number",
                          "minimum": -1000000,
                          "maximum": 1000000,
                          "description": "Preu unitari de venda sense impostos, en euros",
                          "example": 65
                        },
                        "tax_rate": {
                          "type": [
                            "number",
                            "null"
                          ],
                          "minimum": 0,
                          "maximum": 30,
                          "description": "Tipus impositiu; si s'omet, el del taller (IVA/IGIC/IPSI segons la zona)",
                          "example": 21
                        },
                        "discount_pct": {
                          "type": "number",
                          "minimum": 0,
                          "maximum": 100,
                          "description": "Descompte en %",
                          "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": "Referència de recanvi",
                          "example": null
                        },
                        "group_title": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "maxLength": 200,
                          "description": "Títol del grup al qual pertany",
                          "example": null
                        }
                      },
                      "required": [
                        "description",
                        "quantity",
                        "unit_price"
                      ],
                      "additionalProperties": false,
                      "description": "Partida nova"
                    },
                    "description": "Partides inicials; els totals els calcula el servidor"
                  },
                  "notify_client": {
                    "type": "boolean",
                    "description": "Enviar al client l'acusament amb el seu enllaç de seguiment. També exigeix communications:send",
                    "example": false
                  }
                },
                "required": [
                  "client_id"
                ],
                "additionalProperties": false,
                "description": "Pressupost nou; entra en estat «Pendiente» amb canal «API»"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Resposta correcta",
            "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": "Error — bad_request: La petició no és vàlida. | idempotency_key_required: Falta la capçalera Idempotency-Key (obligatòria a tot POST; de 8 a 255 caràcters visibles).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Error — missing_api_key: Falta la clau API: envia-la a la capçalera Authorization: Bearer pt_… o X-Api-Key. | invalid_api_key: La clau API no és vàlida per a aquesta instància.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Error — ip_not_allowed: L'adreça IP d'origen no és a la llista permesa d'aquesta clau. | insufficient_scope: La clau API no té el permís necessari per a aquesta operació. | plan_scope_not_allowed: El pla d'API del taller no inclou aquest permís. Per fer-lo servir cal millorar el pla. | test_key_forbidden: Una clau de prova (pt_test_) no pot fer operacions amb efectes: fes servir una clau live.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Error — vehicle_belongs_to_other_client: Aquesta matrícula ja està donada d'alta a nom d'un altre client. | client_erased: El client va demanar l'esborrat de les seves dades (RGPD): la fitxa no admet canvis ni altes associades. | idempotency_conflict: Aquesta Idempotency-Key ja s'ha fet servir les últimes 24 h amb una altra petició. | idempotency_in_progress: Hi ha una altra petició amb la mateixa Idempotency-Key en curs. Torna-ho a provar d'aquí a uns segons.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "Error — validation_error: El cos de la petició no és vàlid: revisa la llista «fields».",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ValidationError"
                }
              }
            }
          },
          "429": {
            "description": "Error — rate_limited: Has superat el límit de peticions d'aquesta clau. Espera i torna-ho a provar. | plan_quota_exceeded: S'ha esgotat la quota diària de peticions del pla d'API del taller. Es renova a les 00:00 UTC; per a més volum, millora el pla.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Error — internal_error: Error intern.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Error — instance_rate_limited: La instància està rebent massa peticions per API en aquest moment. Torna-ho a provar d'aquí a uns segons.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-client-key": "filtered"
      }
    },
    "/api/v1/budgets/{id}": {
      "get": {
        "operationId": "getBudget",
        "tags": [
          "Pressupostos"
        ],
        "summary": "Detall d'un pressupost",
        "description": "Partides (descripció, quantitat, preu unitari, impost, tipus, descompte), totals amb desglossament, cita, dates d'entrada i sortida, responsable i URL pública de seguiment.\n\nPermís necessari: `budgets:read`.\n\nClau de client: només si el recurs és del seu client; si no, 404.",
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ],
        "x-scope": "budgets:read",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Id del pressupost.",
            "schema": {
              "type": "integer",
              "example": 1234
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Resposta correcta",
            "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": "Error — bad_request: La petició no és vàlida.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Error — missing_api_key: Falta la clau API: envia-la a la capçalera Authorization: Bearer pt_… o X-Api-Key. | invalid_api_key: La clau API no és vàlida per a aquesta instància.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Error — ip_not_allowed: L'adreça IP d'origen no és a la llista permesa d'aquesta clau. | insufficient_scope: La clau API no té el permís necessari per a aquesta operació. | plan_scope_not_allowed: El pla d'API del taller no inclou aquest permís. Per fer-lo servir cal millorar el pla.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Error — not_found: No s'ha trobat el recurs.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Error — rate_limited: Has superat el límit de peticions d'aquesta clau. Espera i torna-ho a provar. | plan_quota_exceeded: S'ha esgotat la quota diària de peticions del pla d'API del taller. Es renova a les 00:00 UTC; per a més volum, millora el pla.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Error — internal_error: Error intern.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Error — instance_rate_limited: La instància està rebent massa peticions per API en aquest moment. Torna-ho a provar d'aquí a uns segons.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-client-key": "own:budget"
      }
    },
    "/api/v1/budgets/{id}/activity": {
      "get": {
        "operationId": "getBudgetActivity",
        "tags": [
          "Pressupostos"
        ],
        "summary": "Historial del pressupost",
        "description": "El que ha passat al pressupost, del més recent al més antic: canvis d'estat, enviaments, acceptació (amb el seu origen), cites, missatges del client, facturació… El mateix registre que veu el taller a la fitxa; public=true marca el que també veu el client. Els codis d'un sol ús i els enllaços d'accés surten ocults.\n\nPermís necessari: `budgets:read`.\n\nClau de client: només si el recurs és del seu client; si no, 404.",
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ],
        "x-scope": "budgets:read",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Id del pressupost.",
            "schema": {
              "type": "integer",
              "example": 1234
            }
          },
          {
            "name": "visibility",
            "in": "query",
            "required": false,
            "description": "all: tot el registre; public: només el que veu el client.",
            "schema": {
              "type": "string",
              "enum": [
                "all",
                "public"
              ],
              "default": "all"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Resultats per pàgina (1–200).",
            "schema": {
              "type": "integer",
              "default": 50,
              "example": 50
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "required": false,
            "description": "Cursor opac retornat a next_cursor de la pàgina anterior.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Resposta correcta",
            "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/ActivityEntry"
                      }
                    },
                    "next_cursor": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "description": "Cursor opac per demanar la pàgina següent; null si no n'hi ha més",
                      "example": "aWQ6MTIzNA"
                    },
                    "has_more": {
                      "type": "boolean",
                      "description": "true si queden més resultats",
                      "example": true
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Error — bad_request: La petició no és vàlida.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Error — missing_api_key: Falta la clau API: envia-la a la capçalera Authorization: Bearer pt_… o X-Api-Key. | invalid_api_key: La clau API no és vàlida per a aquesta instància.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Error — ip_not_allowed: L'adreça IP d'origen no és a la llista permesa d'aquesta clau. | insufficient_scope: La clau API no té el permís necessari per a aquesta operació. | plan_scope_not_allowed: El pla d'API del taller no inclou aquest permís. Per fer-lo servir cal millorar el pla.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Error — not_found: No s'ha trobat el recurs.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Error — rate_limited: Has superat el límit de peticions d'aquesta clau. Espera i torna-ho a provar. | plan_quota_exceeded: S'ha esgotat la quota diària de peticions del pla d'API del taller. Es renova a les 00:00 UTC; per a més volum, millora el pla.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Error — internal_error: Error intern.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Error — instance_rate_limited: La instància està rebent massa peticions per API en aquest moment. Torna-ho a provar d'aquí a uns segons.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-client-key": "own:budget"
      }
    },
    "/api/v1/budgets/{id}/lines": {
      "post": {
        "operationId": "addBudgetLine",
        "tags": [
          "Pressupostos"
        ],
        "summary": "Afegir una partida",
        "description": "La resta de partides conserven el seu id. Deixa instantània prèvia i registre com qualsevol edició del programa. 409 budget_locked si el pressupost és Facturado, Facturado externamente, Cancelado, Rechazado, Desistido o ja té factura.\n\nPermís necessari: `budgets:write`.\n\nClau de client: no disponible (403 client_key_forbidden).",
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ],
        "x-scope": "budgets:write",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Id del pressupost.",
            "schema": {
              "type": "integer",
              "example": 1234
            }
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": true,
            "description": "Clau única per operació (es recomana un UUID). Repetir-la amb el mateix cos en 24 h retorna la resposta desada amb Idempotent-Replay: true; amb un altre cos, 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": "Concepte visible per al client",
                    "example": "Canvi d'oli i filtre"
                  },
                  "quantity": {
                    "type": "number",
                    "minimum": 0,
                    "maximum": 100000,
                    "description": "Quantitat (hores en mà d'obra)",
                    "example": 1
                  },
                  "unit_price": {
                    "type": "number",
                    "minimum": -1000000,
                    "maximum": 1000000,
                    "description": "Preu unitari de venda sense impostos, en euros",
                    "example": 65
                  },
                  "tax_rate": {
                    "type": [
                      "number",
                      "null"
                    ],
                    "minimum": 0,
                    "maximum": 30,
                    "description": "Tipus impositiu; si s'omet, el del taller (IVA/IGIC/IPSI segons la zona)",
                    "example": 21
                  },
                  "discount_pct": {
                    "type": "number",
                    "minimum": 0,
                    "maximum": 100,
                    "description": "Descompte en %",
                    "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": "Referència de recanvi",
                    "example": null
                  },
                  "group_title": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 200,
                    "description": "Títol del grup al qual pertany",
                    "example": null
                  }
                },
                "required": [
                  "description",
                  "quantity",
                  "unit_price"
                ],
                "additionalProperties": false,
                "description": "Partida nova"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Resposta correcta",
            "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": "Partida creada i pressupost amb els totals recalculats",
                  "properties": {
                    "line": {
                      "$ref": "#/components/schemas/BudgetLine"
                    },
                    "budget": {
                      "$ref": "#/components/schemas/BudgetDetail"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Error — bad_request: La petició no és vàlida. | idempotency_key_required: Falta la capçalera Idempotency-Key (obligatòria a tot POST; de 8 a 255 caràcters visibles).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Error — missing_api_key: Falta la clau API: envia-la a la capçalera Authorization: Bearer pt_… o X-Api-Key. | invalid_api_key: La clau API no és vàlida per a aquesta instància.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Error — ip_not_allowed: L'adreça IP d'origen no és a la llista permesa d'aquesta clau. | insufficient_scope: La clau API no té el permís necessari per a aquesta operació. | plan_scope_not_allowed: El pla d'API del taller no inclou aquest permís. Per fer-lo servir cal millorar el pla. | test_key_forbidden: Una clau de prova (pt_test_) no pot fer operacions amb efectes: fes servir una clau live. | client_key_forbidden: Aquesta clau està limitada a un client del taller i no pot fer aquesta operació: només treballa amb les dades del seu client.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Error — not_found: No s'ha trobat el recurs.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Error — budget_locked: El pressupost està tancat i ja no admet aquest canvi. | idempotency_conflict: Aquesta Idempotency-Key ja s'ha fet servir les últimes 24 h amb una altra petició. | idempotency_in_progress: Hi ha una altra petició amb la mateixa Idempotency-Key en curs. Torna-ho a provar d'aquí a uns segons.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "Error — validation_error: El cos de la petició no és vàlid: revisa la llista «fields».",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ValidationError"
                }
              }
            }
          },
          "429": {
            "description": "Error — rate_limited: Has superat el límit de peticions d'aquesta clau. Espera i torna-ho a provar. | plan_quota_exceeded: S'ha esgotat la quota diària de peticions del pla d'API del taller. Es renova a les 00:00 UTC; per a més volum, millora el pla.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Error — internal_error: Error intern.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Error — instance_rate_limited: La instància està rebent massa peticions per API en aquest moment. Torna-ho a provar d'aquí a uns segons.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-client-key": "deny"
      }
    },
    "/api/v1/budgets/{id}/lines/{lineId}": {
      "patch": {
        "operationId": "updateBudgetLine",
        "tags": [
          "Pressupostos"
        ],
        "summary": "Modificar una partida",
        "description": "Permís necessari: `budgets:write`.\n\nClau de client: no disponible (403 client_key_forbidden).",
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ],
        "x-scope": "budgets:write",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Id del pressupost.",
            "schema": {
              "type": "integer",
              "example": 1234
            }
          },
          {
            "name": "lineId",
            "in": "path",
            "required": true,
            "description": "Id de la partida.",
            "schema": {
              "type": "integer",
              "example": 5501
            }
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Clau única per operació (es recomana un UUID). Repetir-la amb el mateix cos en 24 h retorna la resposta desada amb Idempotent-Replay: true; amb un altre cos, 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": "Concepte visible per al client",
                    "example": "Canvi d'oli i filtre"
                  },
                  "quantity": {
                    "type": "number",
                    "minimum": 0,
                    "maximum": 100000,
                    "description": "Quantitat (hores en mà d'obra)",
                    "example": 1
                  },
                  "unit_price": {
                    "type": "number",
                    "minimum": -1000000,
                    "maximum": 1000000,
                    "description": "Preu unitari de venda sense impostos, en euros",
                    "example": 65
                  },
                  "tax_rate": {
                    "type": [
                      "number",
                      "null"
                    ],
                    "minimum": 0,
                    "maximum": 30,
                    "description": "Tipus impositiu; si s'omet, el del taller (IVA/IGIC/IPSI segons la zona)",
                    "example": 21
                  },
                  "discount_pct": {
                    "type": "number",
                    "minimum": 0,
                    "maximum": 100,
                    "description": "Descompte en %",
                    "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": "Referència de recanvi",
                    "example": null
                  },
                  "group_title": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 200,
                    "description": "Títol del grup al qual pertany",
                    "example": null
                  }
                },
                "required": [],
                "additionalProperties": false,
                "description": "Canvis parcials d'una partida"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Resposta correcta",
            "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": "Partida modificada i pressupost recalculat",
                  "properties": {
                    "line": {
                      "$ref": "#/components/schemas/BudgetLine"
                    },
                    "budget": {
                      "$ref": "#/components/schemas/BudgetDetail"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Error — bad_request: La petició no és vàlida.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Error — missing_api_key: Falta la clau API: envia-la a la capçalera Authorization: Bearer pt_… o X-Api-Key. | invalid_api_key: La clau API no és vàlida per a aquesta instància.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Error — ip_not_allowed: L'adreça IP d'origen no és a la llista permesa d'aquesta clau. | insufficient_scope: La clau API no té el permís necessari per a aquesta operació. | plan_scope_not_allowed: El pla d'API del taller no inclou aquest permís. Per fer-lo servir cal millorar el pla. | test_key_forbidden: Una clau de prova (pt_test_) no pot fer operacions amb efectes: fes servir una clau live. | client_key_forbidden: Aquesta clau està limitada a un client del taller i no pot fer aquesta operació: només treballa amb les dades del seu client.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Error — not_found: No s'ha trobat el recurs.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Error — budget_locked: El pressupost està tancat i ja no admet aquest canvi.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "Error — validation_error: El cos de la petició no és vàlid: revisa la llista «fields».",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ValidationError"
                }
              }
            }
          },
          "429": {
            "description": "Error — rate_limited: Has superat el límit de peticions d'aquesta clau. Espera i torna-ho a provar. | plan_quota_exceeded: S'ha esgotat la quota diària de peticions del pla d'API del taller. Es renova a les 00:00 UTC; per a més volum, millora el pla.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Error — internal_error: Error intern.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Error — instance_rate_limited: La instància està rebent massa peticions per API en aquest moment. Torna-ho a provar d'aquí a uns segons.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-client-key": "deny"
      },
      "delete": {
        "operationId": "deleteBudgetLine",
        "tags": [
          "Pressupostos"
        ],
        "summary": "Treure una partida",
        "description": "Retorna el pressupost amb els totals recalculats. La partida queda a la instantània prèvia de l'historial de versions.\n\nPermís necessari: `budgets:write`.\n\nClau de client: no disponible (403 client_key_forbidden).",
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ],
        "x-scope": "budgets:write",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Id del pressupost.",
            "schema": {
              "type": "integer",
              "example": 1234
            }
          },
          {
            "name": "lineId",
            "in": "path",
            "required": true,
            "description": "Id de la partida.",
            "schema": {
              "type": "integer",
              "example": 5501
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Resposta correcta",
            "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": "Error — bad_request: La petició no és vàlida.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Error — missing_api_key: Falta la clau API: envia-la a la capçalera Authorization: Bearer pt_… o X-Api-Key. | invalid_api_key: La clau API no és vàlida per a aquesta instància.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Error — ip_not_allowed: L'adreça IP d'origen no és a la llista permesa d'aquesta clau. | insufficient_scope: La clau API no té el permís necessari per a aquesta operació. | plan_scope_not_allowed: El pla d'API del taller no inclou aquest permís. Per fer-lo servir cal millorar el pla. | test_key_forbidden: Una clau de prova (pt_test_) no pot fer operacions amb efectes: fes servir una clau live. | client_key_forbidden: Aquesta clau està limitada a un client del taller i no pot fer aquesta operació: només treballa amb les dades del seu client.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Error — not_found: No s'ha trobat el recurs.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Error — budget_locked: El pressupost està tancat i ja no admet aquest canvi.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Error — rate_limited: Has superat el límit de peticions d'aquesta clau. Espera i torna-ho a provar. | plan_quota_exceeded: S'ha esgotat la quota diària de peticions del pla d'API del taller. Es renova a les 00:00 UTC; per a més volum, millora el pla.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Error — internal_error: Error intern.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Error — instance_rate_limited: La instància està rebent massa peticions per API en aquest moment. Torna-ho a provar d'aquí a uns segons.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-client-key": "deny"
      }
    },
    "/api/v1/budgets/{id}/status": {
      "post": {
        "operationId": "changeBudgetStatus",
        "tags": [
          "Pressupostos"
        ],
        "summary": "Canviar l'estat",
        "description": "Admet Pendiente/En cotización/Enviado (abans de l'acceptació), En curso (pressupost ja acceptat), Finalizado (des d'Aprobado, En curso o En espera) i Cancelado. «Aprobado» respon 403 client_acceptance_required: l'acceptació la signa el client des de l'enllaç que li envia el taller, o la registra la teva integració en nom seu amb POST /budgets/{id}/accept (permís budgets:accept). Rebutjar va per POST /budgets/{id}/reject i facturar per POST /budgets/{id}/invoice (permís invoices:write); aquí responen 403 status_transition_forbidden, igual que desistir; des de Facturado o Cancelado, 409 budget_locked. «Enviado» exigeix a més communications:send i sent_via: registra que EL TEU sistema ja l'ha enviat (no l'envia). Cap canvi avisa el client excepte Finalizado amb notify_client=true i communications:send.\n\nPermís necessari: `budgets:write`.\n\nClau de client: no disponible (403 client_key_forbidden).",
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ],
        "x-scope": "budgets:write",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Id del pressupost.",
            "schema": {
              "type": "integer",
              "example": 1234
            }
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": true,
            "description": "Clau única per operació (es recomana un UUID). Repetir-la amb el mateix cos en 24 h retorna la resposta desada amb Idempotent-Replay: true; amb un altre cos, 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 o Cancelado",
                    "example": "En curso"
                  },
                  "sent_via": {
                    "type": "string",
                    "enum": [
                      "email",
                      "sms",
                      "whatsapp"
                    ],
                    "description": "Només amb status=Enviado: canal pel qual el TEU sistema ha enviat el pressupost. Exigeix communications:send",
                    "example": "email"
                  },
                  "notify_client": {
                    "type": "boolean",
                    "description": "Amb Finalizado: avisar el client que el vehicle és a punt (segons els avisos configurats). Exigeix communications:send",
                    "example": false
                  }
                },
                "required": [
                  "status"
                ],
                "additionalProperties": false,
                "description": "Canvi d'estat"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Resposta correcta",
            "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": "Error — bad_request: La petició no és vàlida. | idempotency_key_required: Falta la capçalera Idempotency-Key (obligatòria a tot POST; de 8 a 255 caràcters visibles).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Error — missing_api_key: Falta la clau API: envia-la a la capçalera Authorization: Bearer pt_… o X-Api-Key. | invalid_api_key: La clau API no és vàlida per a aquesta instància.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Error — ip_not_allowed: L'adreça IP d'origen no és a la llista permesa d'aquesta clau. | insufficient_scope: La clau API no té el permís necessari per a aquesta operació. | plan_scope_not_allowed: El pla d'API del taller no inclou aquest permís. Per fer-lo servir cal millorar el pla. | test_key_forbidden: Una clau de prova (pt_test_) no pot fer operacions amb efectes: fes servir una clau live. | client_acceptance_required: Per acceptar un pressupost fes servir POST /budgets/{id}/accept amb el permís budgets:accept i l'evidència de qui accepta. | status_transition_forbidden: Aquest canvi d'estat no està disponible per API. | client_key_forbidden: Aquesta clau està limitada a un client del taller i no pot fer aquesta operació: només treballa amb les dades del seu client.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Error — not_found: No s'ha trobat el recurs.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Error — invalid_status_transition: El pressupost no pot passar a aquest estat des de l'estat actual. | budget_locked: El pressupost està tancat i ja no admet aquest canvi. | idempotency_conflict: Aquesta Idempotency-Key ja s'ha fet servir les últimes 24 h amb una altra petició. | idempotency_in_progress: Hi ha una altra petició amb la mateixa Idempotency-Key en curs. Torna-ho a provar d'aquí a uns segons.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "Error — validation_error: El cos de la petició no és vàlid: revisa la llista «fields».",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ValidationError"
                }
              }
            }
          },
          "429": {
            "description": "Error — rate_limited: Has superat el límit de peticions d'aquesta clau. Espera i torna-ho a provar. | plan_quota_exceeded: S'ha esgotat la quota diària de peticions del pla d'API del taller. Es renova a les 00:00 UTC; per a més volum, millora el pla.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Error — internal_error: Error intern.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Error — instance_rate_limited: La instància està rebent massa peticions per API en aquest moment. Torna-ho a provar d'aquí a uns segons.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-client-key": "deny"
      }
    },
    "/api/v1/budgets/{id}/accept": {
      "post": {
        "operationId": "acceptBudget",
        "tags": [
          "Pressupostos"
        ],
        "summary": "Acceptar en nom del client",
        "description": "Per quan el client accepta per una altra via (telèfon, en persona, la teva web…). L'evidència és obligatòria: accepted_by i channel (phone, in_person, email, whatsapp, other); note és opcional. Passa per la mateixa regla d'acceptació del programa: estat Aprobado, transició, dades fiscals mínimes del client (si falten, 422 fiscal_data_incomplete amb missing), descompte de campanya, avís intern al taller, confirmació al client segons els avisos configurats i webhooks lead.accepted i budget.accepted. Només des d'«Enviado» (si ja està acceptat respon 200 amb already=true); Cancelado, Rechazado, Desistido, Finalizado o Facturado responen 409 budget_locked. Si el taller exigeix signatura, l'API accepta igualment i ho deixa escrit: «sense signatura, acceptat per integració» (signature=waived_by_integration). A l'historial queda «Acceptat per API en nom del client · integració <clau> · <accepted_by> per <canal>».\n\nPermís necessari: `budgets:accept`.\n\nClau de client: només si el recurs és del seu client; si no, 404.",
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ],
        "x-scope": "budgets:accept",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Id del pressupost.",
            "schema": {
              "type": "integer",
              "example": 1234
            }
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": true,
            "description": "Clau única per operació (es recomana un UUID). Repetir-la amb el mateix cos en 24 h retorna la resposta desada amb Idempotent-Replay: true; amb un altre cos, 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": {
                  "accepted_by": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 120,
                    "description": "Nom de la persona que accepta (el mateix client o qui el representa)",
                    "example": "Laura Gómez"
                  },
                  "channel": {
                    "type": "string",
                    "enum": [
                      "phone",
                      "in_person",
                      "email",
                      "whatsapp",
                      "other"
                    ],
                    "description": "Canal pel qual el client ha donat la seva acceptació",
                    "example": "phone"
                  },
                  "note": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 500,
                    "description": "Nota opcional (referència de la trucada, del correu…)",
                    "example": "Confirmat per telèfon a les 10:12"
                  }
                },
                "required": [
                  "accepted_by",
                  "channel"
                ],
                "additionalProperties": false,
                "description": "Evidència de l'acceptació en nom del client"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Resposta correcta",
            "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/BudgetDecision"
                }
              }
            }
          },
          "400": {
            "description": "Error — bad_request: La petició no és vàlida. | idempotency_key_required: Falta la capçalera Idempotency-Key (obligatòria a tot POST; de 8 a 255 caràcters visibles).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Error — missing_api_key: Falta la clau API: envia-la a la capçalera Authorization: Bearer pt_… o X-Api-Key. | invalid_api_key: La clau API no és vàlida per a aquesta instància.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Error — ip_not_allowed: L'adreça IP d'origen no és a la llista permesa d'aquesta clau. | insufficient_scope: La clau API no té el permís necessari per a aquesta operació. | plan_scope_not_allowed: El pla d'API del taller no inclou aquest permís. Per fer-lo servir cal millorar el pla. | test_key_forbidden: Una clau de prova (pt_test_) no pot fer operacions amb efectes: fes servir una clau live.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Error — not_found: No s'ha trobat el recurs.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Error — budget_locked: El pressupost està tancat i ja no admet aquest canvi. | invalid_status_transition: El pressupost no pot passar a aquest estat des de l'estat actual. | idempotency_conflict: Aquesta Idempotency-Key ja s'ha fet servir les últimes 24 h amb una altra petició. | idempotency_in_progress: Hi ha una altra petició amb la mateixa Idempotency-Key en curs. Torna-ho a provar d'aquí a uns segons.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "Error — validation_error: El cos de la petició no és vàlid: revisa la llista «fields». | fiscal_data_incomplete: Falten dades fiscals del client per poder facturar: revisa la llista «missing».",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Error — rate_limited: Has superat el límit de peticions d'aquesta clau. Espera i torna-ho a provar. | plan_quota_exceeded: S'ha esgotat la quota diària de peticions del pla d'API del taller. Es renova a les 00:00 UTC; per a més volum, millora el pla.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Error — internal_error: Error intern.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Error — instance_rate_limited: La instància està rebent massa peticions per API en aquest moment. Torna-ho a provar d'aquí a uns segons.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-client-key": "own:budget"
      }
    },
    "/api/v1/budgets/{id}/reject": {
      "post": {
        "operationId": "rejectBudget",
        "tags": [
          "Pressupostos"
        ],
        "summary": "Rebutjar en nom del client",
        "description": "Amb motiu obligatori. Mateixa regla que el rebuig del client: des de Pendiente, En cotización, Enviado o Aprobado (si el vehicle encara no ha entrat). Emet lead.rejected i budget.rejected i queda a l'historial amb el nom de la clau.\n\nPermís necessari: `budgets:accept`.\n\nClau de client: només si el recurs és del seu client; si no, 404.",
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ],
        "x-scope": "budgets:accept",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Id del pressupost.",
            "schema": {
              "type": "integer",
              "example": 1234
            }
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": true,
            "description": "Clau única per operació (es recomana un UUID). Repetir-la amb el mateix cos en 24 h retorna la resposta desada amb Idempotent-Replay: true; amb un altre cos, 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": {
                  "reason": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 400,
                    "description": "Motiu del rebuig",
                    "example": "Ho farà en un altre taller"
                  },
                  "rejected_by": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 120,
                    "description": "Qui ho comunica",
                    "example": "Laura Gómez"
                  },
                  "channel": {
                    "type": "string",
                    "enum": [
                      "phone",
                      "in_person",
                      "email",
                      "whatsapp",
                      "other"
                    ],
                    "description": "Canal pel qual el client ho ha comunicat",
                    "example": "phone"
                  }
                },
                "required": [
                  "reason"
                ],
                "additionalProperties": false,
                "description": "Rebuig en nom del client"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Resposta correcta",
            "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/BudgetDecision"
                }
              }
            }
          },
          "400": {
            "description": "Error — bad_request: La petició no és vàlida. | idempotency_key_required: Falta la capçalera Idempotency-Key (obligatòria a tot POST; de 8 a 255 caràcters visibles).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Error — missing_api_key: Falta la clau API: envia-la a la capçalera Authorization: Bearer pt_… o X-Api-Key. | invalid_api_key: La clau API no és vàlida per a aquesta instància.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Error — ip_not_allowed: L'adreça IP d'origen no és a la llista permesa d'aquesta clau. | insufficient_scope: La clau API no té el permís necessari per a aquesta operació. | plan_scope_not_allowed: El pla d'API del taller no inclou aquest permís. Per fer-lo servir cal millorar el pla. | test_key_forbidden: Una clau de prova (pt_test_) no pot fer operacions amb efectes: fes servir una clau live.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Error — not_found: No s'ha trobat el recurs.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Error — budget_locked: El pressupost està tancat i ja no admet aquest canvi. | invalid_status_transition: El pressupost no pot passar a aquest estat des de l'estat actual. | idempotency_conflict: Aquesta Idempotency-Key ja s'ha fet servir les últimes 24 h amb una altra petició. | idempotency_in_progress: Hi ha una altra petició amb la mateixa Idempotency-Key en curs. Torna-ho a provar d'aquí a uns segons.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "Error — validation_error: El cos de la petició no és vàlid: revisa la llista «fields».",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ValidationError"
                }
              }
            }
          },
          "429": {
            "description": "Error — rate_limited: Has superat el límit de peticions d'aquesta clau. Espera i torna-ho a provar. | plan_quota_exceeded: S'ha esgotat la quota diària de peticions del pla d'API del taller. Es renova a les 00:00 UTC; per a més volum, millora el pla.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Error — internal_error: Error intern.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Error — instance_rate_limited: La instància està rebent massa peticions per API en aquest moment. Torna-ho a provar d'aquí a uns segons.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-client-key": "own:budget"
      }
    },
    "/api/v1/budgets/{id}/invoice": {
      "post": {
        "operationId": "invoiceBudget",
        "tags": [
          "Factures"
        ],
        "summary": "Facturar un pressupost",
        "description": "Fa servir la mateixa facturació del programa: copia les partides i l'impost del pressupost, crea l'assentament comptable i, amb issue=true (per defecte), emet la factura amb la sèrie que toqui, número correlatiu, hash encadenat i QR Verifactu. El pressupost passa a Facturado. Es factura un d'Aprobado, En curso, En espera o Finalizado amb import; si ja té factura es retorna aquesta (created=false), mai una segona. Emet invoice.created i invoice.issued. Les factures rectificatives no estan disponibles per API: es fan des del programa.\n\nPermís necessari: `invoices:write`.\n\nClau de client: no disponible (403 client_key_forbidden).",
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ],
        "x-scope": "invoices:write",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Id del pressupost.",
            "schema": {
              "type": "integer",
              "example": 1234
            }
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": true,
            "description": "Clau única per operació (es recomana un UUID). Repetir-la amb el mateix cos en 24 h retorna la resposta desada amb Idempotent-Replay: true; amb un altre cos, 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": {
                  "issue": {
                    "type": "boolean",
                    "description": "true (per defecte): emet la factura amb número definitiu, sèrie, hash Verifactu i assentament comptable. false: la deixa en esborrany per revisar-la al programa",
                    "example": true
                  },
                  "pay_method": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 40,
                    "description": "Forma de pagament que figura a la factura (per defecte CAJA)",
                    "example": "TARJETA"
                  }
                },
                "required": [],
                "additionalProperties": false,
                "description": "Factura d'un pressupost"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Resposta correcta",
            "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/InvoiceCreated"
                }
              }
            }
          },
          "400": {
            "description": "Error — bad_request: La petició no és vàlida. | idempotency_key_required: Falta la capçalera Idempotency-Key (obligatòria a tot POST; de 8 a 255 caràcters visibles).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Error — missing_api_key: Falta la clau API: envia-la a la capçalera Authorization: Bearer pt_… o X-Api-Key. | invalid_api_key: La clau API no és vàlida per a aquesta instància.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Error — ip_not_allowed: L'adreça IP d'origen no és a la llista permesa d'aquesta clau. | insufficient_scope: La clau API no té el permís necessari per a aquesta operació. | plan_scope_not_allowed: El pla d'API del taller no inclou aquest permís. Per fer-lo servir cal millorar el pla. | test_key_forbidden: Una clau de prova (pt_test_) no pot fer operacions amb efectes: fes servir una clau live. | client_key_forbidden: Aquesta clau està limitada a un client del taller i no pot fer aquesta operació: només treballa amb les dades del seu client.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Error — not_found: No s'ha trobat el recurs.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Error — budget_not_invoiceable: El pressupost no es pot facturar en el seu estat actual. | idempotency_conflict: Aquesta Idempotency-Key ja s'ha fet servir les últimes 24 h amb una altra petició. | idempotency_in_progress: Hi ha una altra petició amb la mateixa Idempotency-Key en curs. Torna-ho a provar d'aquí a uns segons.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "Error — validation_error: El cos de la petició no és vàlid: revisa la llista «fields». | invoice_rejected: El programa no ha pogut emetre la factura: revisa el missatge.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Error — rate_limited: Has superat el límit de peticions d'aquesta clau. Espera i torna-ho a provar. | plan_quota_exceeded: S'ha esgotat la quota diària de peticions del pla d'API del taller. Es renova a les 00:00 UTC; per a més volum, millora el pla.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Error — internal_error: Error intern.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Error — instance_rate_limited: La instància està rebent massa peticions per API en aquest moment. Torna-ho a provar d'aquí a uns segons.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-client-key": "deny"
      }
    },
    "/api/v1/budgets/{id}/documents": {
      "post": {
        "operationId": "uploadBudgetDocument",
        "tags": [
          "Pressupostos"
        ],
        "summary": "Adjuntar un document",
        "description": "multipart/form-data amb el camp «file» (JPEG, PNG, WebP o PDF, comprovat pel contingut; màxim 4 MB). Per defecte només el veu el taller; client_visible=true el mostra a l'enllaç de seguiment i mechanic_visible=true a l'app del mecànic. L'empremta d'idempotència inclou el fitxer.\n\nPermís necessari: `budgets:write`.\n\nClau de client: només si el recurs és del seu client; si no, 404.",
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ],
        "x-scope": "budgets:write",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Id del pressupost.",
            "schema": {
              "type": "integer",
              "example": 1234
            }
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": true,
            "description": "Clau única per operació (es recomana un UUID). Repetir-la amb el mateix cos en 24 h retorna la resposta desada amb Idempotent-Replay: true; amb un altre cos, 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 correcta",
            "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": "Error — bad_request: La petició no és vàlida. | idempotency_key_required: Falta la capçalera Idempotency-Key (obligatòria a tot POST; de 8 a 255 caràcters visibles).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Error — missing_api_key: Falta la clau API: envia-la a la capçalera Authorization: Bearer pt_… o X-Api-Key. | invalid_api_key: La clau API no és vàlida per a aquesta instància.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Error — ip_not_allowed: L'adreça IP d'origen no és a la llista permesa d'aquesta clau. | insufficient_scope: La clau API no té el permís necessari per a aquesta operació. | plan_scope_not_allowed: El pla d'API del taller no inclou aquest permís. Per fer-lo servir cal millorar el pla. | test_key_forbidden: Una clau de prova (pt_test_) no pot fer operacions amb efectes: fes servir una clau live.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Error — not_found: No s'ha trobat el recurs.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Error — idempotency_conflict: Aquesta Idempotency-Key ja s'ha fet servir les últimes 24 h amb una altra petició. | idempotency_in_progress: Hi ha una altra petició amb la mateixa Idempotency-Key en curs. Torna-ho a provar d'aquí a uns segons.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "413": {
            "description": "Error — payload_too_large: El fitxer supera la mida màxima (4 MB).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "415": {
            "description": "Error — unsupported_media_type: Tipus de fitxer no admès: només JPEG, PNG, WebP o PDF.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "Error — validation_error: El cos de la petició no és vàlid: revisa la llista «fields».",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ValidationError"
                }
              }
            }
          },
          "429": {
            "description": "Error — rate_limited: Has superat el límit de peticions d'aquesta clau. Espera i torna-ho a provar. | plan_quota_exceeded: S'ha esgotat la quota diària de peticions del pla d'API del taller. Es renova a les 00:00 UTC; per a més volum, millora el pla.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Error — internal_error: Error intern.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Error — instance_rate_limited: La instància està rebent massa peticions per API en aquest moment. Torna-ho a provar d'aquí a uns segons.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-client-key": "own:budget"
      }
    },
    "/api/v1/invoices": {
      "get": {
        "operationId": "listInvoices",
        "tags": [
          "Factures"
        ],
        "summary": "Llistar factures",
        "description": "Factures emeses i esborranys, ordenades per id. Inclou el resum de cobraments.\n\nPermís necessari: `invoices:read`.\n\nClau de client: només retorna o crea dades del seu client.",
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ],
        "x-scope": "invoices:read",
        "parameters": [
          {
            "name": "from",
            "in": "query",
            "required": false,
            "description": "Data de factura des de.",
            "schema": {
              "type": "string",
              "format": "date-time",
              "example": "2026-10-01"
            }
          },
          {
            "name": "to",
            "in": "query",
            "required": false,
            "description": "Data de factura fins a.",
            "schema": {
              "type": "string",
              "format": "date-time",
              "example": "2026-10-31"
            }
          },
          {
            "name": "client_id",
            "in": "query",
            "required": false,
            "description": "Filtrar per client.",
            "schema": {
              "type": "integer",
              "example": 1204
            }
          },
          {
            "name": "status",
            "in": "query",
            "required": false,
            "description": "Estat de la factura.",
            "schema": {
              "type": "string",
              "enum": [
                "draft",
                "issued",
                "cancelled"
              ]
            }
          },
          {
            "name": "series",
            "in": "query",
            "required": false,
            "description": "Sèrie exacta.",
            "schema": {
              "type": "string",
              "example": "F26"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Resultats per pàgina (1–200).",
            "schema": {
              "type": "integer",
              "default": 50,
              "example": 50
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "required": false,
            "description": "Cursor opac retornat a next_cursor de la pàgina anterior.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Resposta correcta",
            "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 opac per demanar la pàgina següent; null si no n'hi ha més",
                      "example": "aWQ6MTIzNA"
                    },
                    "has_more": {
                      "type": "boolean",
                      "description": "true si queden més resultats",
                      "example": true
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Error — bad_request: La petició no és vàlida.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Error — missing_api_key: Falta la clau API: envia-la a la capçalera Authorization: Bearer pt_… o X-Api-Key. | invalid_api_key: La clau API no és vàlida per a aquesta instància.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Error — ip_not_allowed: L'adreça IP d'origen no és a la llista permesa d'aquesta clau. | insufficient_scope: La clau API no té el permís necessari per a aquesta operació. | plan_scope_not_allowed: El pla d'API del taller no inclou aquest permís. Per fer-lo servir cal millorar el pla.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Error — rate_limited: Has superat el límit de peticions d'aquesta clau. Espera i torna-ho a provar. | plan_quota_exceeded: S'ha esgotat la quota diària de peticions del pla d'API del taller. Es renova a les 00:00 UTC; per a més volum, millora el pla.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Error — internal_error: Error intern.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Error — instance_rate_limited: La instància està rebent massa peticions per API en aquest moment. Torna-ho a provar d'aquí a uns segons.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-client-key": "filtered"
      }
    },
    "/api/v1/invoices/{id}": {
      "get": {
        "operationId": "getInvoice",
        "tags": [
          "Factures"
        ],
        "summary": "Detall d'una factura",
        "description": "Permís necessari: `invoices:read`.\n\nClau de client: només si el recurs és del seu client; si no, 404.",
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ],
        "x-scope": "invoices:read",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Id de la factura.",
            "schema": {
              "type": "integer",
              "example": 1234
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Resposta correcta",
            "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": "Error — bad_request: La petició no és vàlida.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Error — missing_api_key: Falta la clau API: envia-la a la capçalera Authorization: Bearer pt_… o X-Api-Key. | invalid_api_key: La clau API no és vàlida per a aquesta instància.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Error — ip_not_allowed: L'adreça IP d'origen no és a la llista permesa d'aquesta clau. | insufficient_scope: La clau API no té el permís necessari per a aquesta operació. | plan_scope_not_allowed: El pla d'API del taller no inclou aquest permís. Per fer-lo servir cal millorar el pla.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Error — not_found: No s'ha trobat el recurs.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Error — rate_limited: Has superat el límit de peticions d'aquesta clau. Espera i torna-ho a provar. | plan_quota_exceeded: S'ha esgotat la quota diària de peticions del pla d'API del taller. Es renova a les 00:00 UTC; per a més volum, millora el pla.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Error — internal_error: Error intern.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Error — instance_rate_limited: La instància està rebent massa peticions per API en aquest moment. Torna-ho a provar d'aquí a uns segons.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-client-key": "own:invoice"
      }
    },
    "/api/v1/invoices/{id}/pdf": {
      "get": {
        "operationId": "getInvoicePdf",
        "tags": [
          "Factures"
        ],
        "summary": "PDF d'una factura",
        "description": "El mateix PDF que genera el programa (amb QR Verifactu si la factura està emesa). Resposta application/pdf.\n\nPermís necessari: `invoices:read`.\n\nClau de client: només si el recurs és del seu client; si no, 404.",
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ],
        "x-scope": "invoices:read",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Id de la factura.",
            "schema": {
              "type": "integer",
              "example": 1234
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Resposta correcta",
            "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": "Error — bad_request: La petició no és vàlida.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Error — missing_api_key: Falta la clau API: envia-la a la capçalera Authorization: Bearer pt_… o X-Api-Key. | invalid_api_key: La clau API no és vàlida per a aquesta instància.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Error — ip_not_allowed: L'adreça IP d'origen no és a la llista permesa d'aquesta clau. | insufficient_scope: La clau API no té el permís necessari per a aquesta operació. | plan_scope_not_allowed: El pla d'API del taller no inclou aquest permís. Per fer-lo servir cal millorar el pla.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Error — not_found: No s'ha trobat el recurs.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Error — rate_limited: Has superat el límit de peticions d'aquesta clau. Espera i torna-ho a provar. | plan_quota_exceeded: S'ha esgotat la quota diària de peticions del pla d'API del taller. Es renova a les 00:00 UTC; per a més volum, millora el pla.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Error — internal_error: Error intern.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Error — instance_rate_limited: La instància està rebent massa peticions per API en aquest moment. Torna-ho a provar d'aquí a uns segons.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-client-key": "own:invoice"
      }
    },
    "/api/v1/invoices/{id}/payments": {
      "post": {
        "operationId": "addInvoicePayment",
        "tags": [
          "Factures"
        ],
        "summary": "Anotar un cobrament",
        "description": "Mateix registre de cobraments que la fitxa de la factura: la marca «pagada» es recalcula amb tots els cobraments (mai es força) i s'emet payment.received. Només sobre factures emeses. Retorna la factura amb el seu resum de cobraments.\n\nPermís necessari: `invoices:write`.\n\nClau de client: no disponible (403 client_key_forbidden).",
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ],
        "x-scope": "invoices:write",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Id de la factura.",
            "schema": {
              "type": "integer",
              "example": 1234
            }
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": true,
            "description": "Clau única per operació (es recomana un UUID). Repetir-la amb el mateix cos en 24 h retorna la resposta desada amb Idempotent-Replay: true; amb un altre cos, 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": {
                  "amount": {
                    "type": "number",
                    "minimum": 0.01,
                    "maximum": 1000000,
                    "description": "Import cobrat en euros",
                    "example": 245.3
                  },
                  "date": {
                    "type": "string",
                    "format": "date",
                    "description": "Data del cobrament (avui per defecte)",
                    "example": "2026-10-13"
                  },
                  "pay_method": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 40,
                    "description": "Forma de pagament",
                    "example": "TARJETA"
                  },
                  "notes": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 500,
                    "example": "Cobrat al taulell"
                  }
                },
                "required": [
                  "amount"
                ],
                "additionalProperties": false,
                "description": "Cobrament d'una factura"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Resposta correcta",
            "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": "Error — bad_request: La petició no és vàlida. | idempotency_key_required: Falta la capçalera Idempotency-Key (obligatòria a tot POST; de 8 a 255 caràcters visibles).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Error — missing_api_key: Falta la clau API: envia-la a la capçalera Authorization: Bearer pt_… o X-Api-Key. | invalid_api_key: La clau API no és vàlida per a aquesta instància.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Error — ip_not_allowed: L'adreça IP d'origen no és a la llista permesa d'aquesta clau. | insufficient_scope: La clau API no té el permís necessari per a aquesta operació. | plan_scope_not_allowed: El pla d'API del taller no inclou aquest permís. Per fer-lo servir cal millorar el pla. | test_key_forbidden: Una clau de prova (pt_test_) no pot fer operacions amb efectes: fes servir una clau live. | client_key_forbidden: Aquesta clau està limitada a un client del taller i no pot fer aquesta operació: només treballa amb les dades del seu client.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Error — not_found: No s'ha trobat el recurs.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Error — idempotency_conflict: Aquesta Idempotency-Key ja s'ha fet servir les últimes 24 h amb una altra petició. | idempotency_in_progress: Hi ha una altra petició amb la mateixa Idempotency-Key en curs. Torna-ho a provar d'aquí a uns segons.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "Error — validation_error: El cos de la petició no és vàlid: revisa la llista «fields». | invoice_rejected: El programa no ha pogut emetre la factura: revisa el missatge.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Error — rate_limited: Has superat el límit de peticions d'aquesta clau. Espera i torna-ho a provar. | plan_quota_exceeded: S'ha esgotat la quota diària de peticions del pla d'API del taller. Es renova a les 00:00 UTC; per a més volum, millora el pla.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Error — internal_error: Error intern.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Error — instance_rate_limited: La instància està rebent massa peticions per API en aquest moment. Torna-ho a provar d'aquí a uns segons.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-client-key": "deny"
      }
    },
    "/api/v1/appointments": {
      "get": {
        "operationId": "listAppointments",
        "tags": [
          "Cites"
        ],
        "summary": "Cites confirmades i proposades",
        "description": "Per defecte els propers 30 dies. status=confirmed són cites fixades a l'agenda; status=proposed són propostes del client pendents que el taller confirmi (bloquegen el forat).\n\nPermís necessari: `appointments:read`.\n\nClau de client: només retorna o crea dades del seu client.",
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ],
        "x-scope": "appointments:read",
        "parameters": [
          {
            "name": "from",
            "in": "query",
            "required": false,
            "description": "Inici del rang (per defecte ara).",
            "schema": {
              "type": "string",
              "format": "date-time",
              "example": "2026-10-01"
            }
          },
          {
            "name": "to",
            "in": "query",
            "required": false,
            "description": "Fi del rang (per defecte +30 dies, màxim 1 any).",
            "schema": {
              "type": "string",
              "format": "date-time",
              "example": "2026-10-31"
            }
          },
          {
            "name": "status",
            "in": "query",
            "required": false,
            "description": "Només un tipus.",
            "schema": {
              "type": "string",
              "enum": [
                "confirmed",
                "proposed"
              ]
            }
          },
          {
            "name": "box_id",
            "in": "query",
            "required": false,
            "description": "Filtrar per elevador/box.",
            "schema": {
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Resposta correcta",
            "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": "Error — bad_request: La petició no és vàlida.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Error — missing_api_key: Falta la clau API: envia-la a la capçalera Authorization: Bearer pt_… o X-Api-Key. | invalid_api_key: La clau API no és vàlida per a aquesta instància.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Error — ip_not_allowed: L'adreça IP d'origen no és a la llista permesa d'aquesta clau. | insufficient_scope: La clau API no té el permís necessari per a aquesta operació. | plan_scope_not_allowed: El pla d'API del taller no inclou aquest permís. Per fer-lo servir cal millorar el pla.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Error — rate_limited: Has superat el límit de peticions d'aquesta clau. Espera i torna-ho a provar. | plan_quota_exceeded: S'ha esgotat la quota diària de peticions del pla d'API del taller. Es renova a les 00:00 UTC; per a més volum, millora el pla.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Error — internal_error: Error intern.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Error — instance_rate_limited: La instància està rebent massa peticions per API en aquest moment. Torna-ho a provar d'aquí a uns segons.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-client-key": "filtered"
      },
      "post": {
        "operationId": "createAppointment",
        "tags": [
          "Cites"
        ],
        "summary": "Proposar o reservar una cita",
        "description": "Segueix el mode de reserva del taller (booking_mode a GET /workshop). En «propose» es crea una proposta (status=proposed) que bloqueja el forat fins que el taller la confirma; demanar mode=book respon 403 booking_mode_propose_only. En «book» la cita queda ferma (status=confirmed), tret que demanis mode=propose. El forat es valida amb la mateixa lògica que /appointments/availability; si no és lliure, 409 slot_unavailable amb fins a 3 alternatives a error.alternatives. No s'avisa el client tret de notify_client=true amb communications:send.\n\nPermís necessari: `appointments:write`.\n\nClau de client: només retorna o crea dades del seu client.",
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ],
        "x-scope": "appointments:write",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": true,
            "description": "Clau única per operació (es recomana un UUID). Repetir-la amb el mateix cos en 24 h retorna la resposta desada amb Idempotent-Replay: true; amb un altre cos, 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": "Pressupost al qual pertany la cita",
                    "example": 1234
                  },
                  "start": {
                    "type": "string",
                    "format": "date-time",
                    "description": "Inici amb zona horària (ISO 8601)",
                    "example": "2026-10-14T09:00:00+02:00"
                  },
                  "box_id": {
                    "type": "integer",
                    "minimum": 1,
                    "description": "Elevador/box; si s'omet, el primer lliure",
                    "example": 2
                  },
                  "duration_minutes": {
                    "type": "integer",
                    "minimum": 15,
                    "maximum": 720,
                    "description": "Durada; per defecte la mínima de l'agenda",
                    "example": 60
                  },
                  "mode": {
                    "type": "string",
                    "enum": [
                      "propose",
                      "book"
                    ],
                    "description": "propose: proposta que el taller confirma. book: cita ferma (només si el taller treballa en mode «reservar»). Per defecte, el mode del taller",
                    "example": "propose"
                  },
                  "notify_client": {
                    "type": "boolean",
                    "description": "Enviar la confirmació de cita al client (només cites fermes). Exigeix communications:send",
                    "example": false
                  }
                },
                "required": [
                  "budget_id",
                  "start"
                ],
                "additionalProperties": false,
                "description": "Cita o proposta de cita"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Resposta correcta",
            "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": "Error — bad_request: La petició no és vàlida. | idempotency_key_required: Falta la capçalera Idempotency-Key (obligatòria a tot POST; de 8 a 255 caràcters visibles).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Error — missing_api_key: Falta la clau API: envia-la a la capçalera Authorization: Bearer pt_… o X-Api-Key. | invalid_api_key: La clau API no és vàlida per a aquesta instància.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Error — ip_not_allowed: L'adreça IP d'origen no és a la llista permesa d'aquesta clau. | insufficient_scope: La clau API no té el permís necessari per a aquesta operació. | plan_scope_not_allowed: El pla d'API del taller no inclou aquest permís. Per fer-lo servir cal millorar el pla. | test_key_forbidden: Una clau de prova (pt_test_) no pot fer operacions amb efectes: fes servir una clau live. | booking_mode_propose_only: El taller treballa en mode «proposar cita»: només es poden crear propostes que el taller confirma.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Error — slot_unavailable: Aquest forat no està disponible. | budget_locked: El pressupost està tancat i ja no admet aquest canvi. | idempotency_conflict: Aquesta Idempotency-Key ja s'ha fet servir les últimes 24 h amb una altra petició. | idempotency_in_progress: Hi ha una altra petició amb la mateixa Idempotency-Key en curs. Torna-ho a provar d'aquí a uns segons.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "Error — validation_error: El cos de la petició no és vàlid: revisa la llista «fields».",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ValidationError"
                }
              }
            }
          },
          "429": {
            "description": "Error — rate_limited: Has superat el límit de peticions d'aquesta clau. Espera i torna-ho a provar. | plan_quota_exceeded: S'ha esgotat la quota diària de peticions del pla d'API del taller. Es renova a les 00:00 UTC; per a més volum, millora el pla.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Error — internal_error: Error intern.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Error — instance_rate_limited: La instància està rebent massa peticions per API en aquest moment. Torna-ho a provar d'aquí a uns segons.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-client-key": "filtered"
      }
    },
    "/api/v1/appointments/availability": {
      "get": {
        "operationId": "getAvailability",
        "tags": [
          "Cites"
        ],
        "summary": "Forats lliures",
        "description": "Mateixa lògica que l'agenda i la web de seguiment: horari del taller i de cada box, dinar, festius nacionals, autonòmics i locals, cites obertes i propostes pendents (que bloquegen el seu forat). Inicis cada 30 min i com a mínim 1 h des d'ara. Per defecte els propers 7 dies (màxim 31). Inclou el booking_mode del taller.\n\nPermís necessari: `appointments:read`.\n\nClau de client: permès (no porta dades d'altres clients).",
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ],
        "x-scope": "appointments:read",
        "parameters": [
          {
            "name": "from",
            "in": "query",
            "required": false,
            "description": "Des de (per defecte ara).",
            "schema": {
              "type": "string",
              "format": "date-time",
              "example": "2026-10-01"
            }
          },
          {
            "name": "to",
            "in": "query",
            "required": false,
            "description": "Fins a (per defecte +7 dies; màxim 31 dies).",
            "schema": {
              "type": "string",
              "format": "date-time",
              "example": "2026-10-31"
            }
          },
          {
            "name": "duration_minutes",
            "in": "query",
            "required": false,
            "description": "Durada de la cita (15–720). Per defecte la mínima de l'agenda.",
            "schema": {
              "type": "integer",
              "example": 60
            }
          },
          {
            "name": "box_id",
            "in": "query",
            "required": false,
            "description": "Només aquest box.",
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Màxim de forats (1–500).",
            "schema": {
              "type": "integer",
              "default": 100
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Resposta correcta",
            "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": "Error — bad_request: La petició no és vàlida.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Error — missing_api_key: Falta la clau API: envia-la a la capçalera Authorization: Bearer pt_… o X-Api-Key. | invalid_api_key: La clau API no és vàlida per a aquesta instància.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Error — ip_not_allowed: L'adreça IP d'origen no és a la llista permesa d'aquesta clau. | insufficient_scope: La clau API no té el permís necessari per a aquesta operació. | plan_scope_not_allowed: El pla d'API del taller no inclou aquest permís. Per fer-lo servir cal millorar el pla.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Error — rate_limited: Has superat el límit de peticions d'aquesta clau. Espera i torna-ho a provar. | plan_quota_exceeded: S'ha esgotat la quota diària de peticions del pla d'API del taller. Es renova a les 00:00 UTC; per a més volum, millora el pla.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Error — internal_error: Error intern.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Error — instance_rate_limited: La instància està rebent massa peticions per API en aquest moment. Torna-ho a provar d'aquí a uns segons.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-client-key": "open"
      }
    },
    "/api/v1/appointments/{id}": {
      "delete": {
        "operationId": "cancelAppointment",
        "tags": [
          "Cites"
        ],
        "summary": "Anul·lar una cita o retirar una proposta",
        "description": "b<pressupost>: anul·la la cita confirmada (igual que «Cancel·lar cita» a la fitxa). p<proposta>: retira la proposta pendent; amb notify_client=true i communications:send es convida el client a triar una altra hora. Emet appointment.cancelled.\n\nPermís necessari: `appointments:write`.\n\nClau de client: només si el recurs és del seu client; si no, 404.",
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ],
        "x-scope": "appointments:write",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Id de la cita (b1234 o p88).",
            "schema": {
              "type": "string",
              "example": "b1234"
            }
          },
          {
            "name": "notify_client",
            "in": "query",
            "required": false,
            "description": "Només propostes: avisar el client.",
            "schema": {
              "type": "boolean",
              "default": "false"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Resposta correcta",
            "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": "Error — bad_request: La petició no és vàlida.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Error — missing_api_key: Falta la clau API: envia-la a la capçalera Authorization: Bearer pt_… o X-Api-Key. | invalid_api_key: La clau API no és vàlida per a aquesta instància.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Error — ip_not_allowed: L'adreça IP d'origen no és a la llista permesa d'aquesta clau. | insufficient_scope: La clau API no té el permís necessari per a aquesta operació. | plan_scope_not_allowed: El pla d'API del taller no inclou aquest permís. Per fer-lo servir cal millorar el pla. | test_key_forbidden: Una clau de prova (pt_test_) no pot fer operacions amb efectes: fes servir una clau live.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Error — not_found: No s'ha trobat el recurs.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Error — rate_limited: Has superat el límit de peticions d'aquesta clau. Espera i torna-ho a provar. | plan_quota_exceeded: S'ha esgotat la quota diària de peticions del pla d'API del taller. Es renova a les 00:00 UTC; per a més volum, millora el pla.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Error — internal_error: Error intern.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Error — instance_rate_limited: La instància està rebent massa peticions per API en aquest moment. Torna-ho a provar d'aquí a uns segons.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-client-key": "own:appointment"
      },
      "get": {
        "operationId": "getAppointment",
        "tags": [
          "Cites"
        ],
        "summary": "Detall d'una cita",
        "description": "Permís necessari: `appointments:read`.\n\nClau de client: només si el recurs és del seu client; si no, 404.",
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ],
        "x-scope": "appointments:read",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Id de la cita (b1234 o p88).",
            "schema": {
              "type": "string",
              "example": "b1234"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Resposta correcta",
            "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": "Error — bad_request: La petició no és vàlida.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Error — missing_api_key: Falta la clau API: envia-la a la capçalera Authorization: Bearer pt_… o X-Api-Key. | invalid_api_key: La clau API no és vàlida per a aquesta instància.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Error — ip_not_allowed: L'adreça IP d'origen no és a la llista permesa d'aquesta clau. | insufficient_scope: La clau API no té el permís necessari per a aquesta operació. | plan_scope_not_allowed: El pla d'API del taller no inclou aquest permís. Per fer-lo servir cal millorar el pla.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Error — not_found: No s'ha trobat el recurs.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Error — rate_limited: Has superat el límit de peticions d'aquesta clau. Espera i torna-ho a provar. | plan_quota_exceeded: S'ha esgotat la quota diària de peticions del pla d'API del taller. Es renova a les 00:00 UTC; per a més volum, millora el pla.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Error — internal_error: Error intern.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Error — instance_rate_limited: La instància està rebent massa peticions per API en aquest moment. Torna-ho a provar d'aquí a uns segons.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-client-key": "own:appointment"
      },
      "patch": {
        "operationId": "updateAppointment",
        "tags": [
          "Cites"
        ],
        "summary": "Moure una cita o canviar una proposta",
        "description": "b<pressupost>: mou la cita en ferm a la nova hora (només si el taller treballa en mode «reservar»; en mode «proposar» respon 403 booking_mode_propose_only: crea una proposta amb POST /appointments). p<proposta>: retira la proposta i en crea una altra a la nova hora (canvia l'id). El forat es valida igual que en crear (409 slot_unavailable amb alternatives). Emet appointment.updated.\n\nPermís necessari: `appointments:write`.\n\nClau de client: només si el recurs és del seu client; si no, 404.",
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ],
        "x-scope": "appointments:write",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Id de la cita (b1234 o p88).",
            "schema": {
              "type": "string",
              "example": "b1234"
            }
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Clau única per operació (es recomana un UUID). Repetir-la amb el mateix cos en 24 h retorna la resposta desada amb Idempotent-Replay: true; amb un altre cos, 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": {
                  "start": {
                    "type": "string",
                    "format": "date-time",
                    "description": "Nou inici amb zona horària (ISO 8601)",
                    "example": "2026-10-15T10:00:00+02:00"
                  },
                  "box_id": {
                    "type": "integer",
                    "minimum": 1,
                    "description": "Elevador/box; si s'omet, el primer lliure",
                    "example": 2
                  },
                  "duration_minutes": {
                    "type": "integer",
                    "minimum": 15,
                    "maximum": 720,
                    "description": "Durada; per defecte la mínima de l'agenda",
                    "example": 60
                  },
                  "notify_client": {
                    "type": "boolean",
                    "description": "Avisar el client de la nova hora (només cites en ferm). Exigeix communications:send",
                    "example": false
                  }
                },
                "required": [
                  "start"
                ],
                "additionalProperties": false,
                "description": "Moure una cita o canviar una proposta"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Resposta correcta",
            "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": "Error — bad_request: La petició no és vàlida.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Error — missing_api_key: Falta la clau API: envia-la a la capçalera Authorization: Bearer pt_… o X-Api-Key. | invalid_api_key: La clau API no és vàlida per a aquesta instància.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Error — ip_not_allowed: L'adreça IP d'origen no és a la llista permesa d'aquesta clau. | insufficient_scope: La clau API no té el permís necessari per a aquesta operació. | plan_scope_not_allowed: El pla d'API del taller no inclou aquest permís. Per fer-lo servir cal millorar el pla. | test_key_forbidden: Una clau de prova (pt_test_) no pot fer operacions amb efectes: fes servir una clau live. | booking_mode_propose_only: El taller treballa en mode «proposar cita»: només es poden crear propostes que el taller confirma.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Error — not_found: No s'ha trobat el recurs.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Error — slot_unavailable: Aquest forat no està disponible. | budget_locked: El pressupost està tancat i ja no admet aquest canvi.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "Error — validation_error: El cos de la petició no és vàlid: revisa la llista «fields».",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ValidationError"
                }
              }
            }
          },
          "429": {
            "description": "Error — rate_limited: Has superat el límit de peticions d'aquesta clau. Espera i torna-ho a provar. | plan_quota_exceeded: S'ha esgotat la quota diària de peticions del pla d'API del taller. Es renova a les 00:00 UTC; per a més volum, millora el pla.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Error — internal_error: Error intern.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Error — instance_rate_limited: La instància està rebent massa peticions per API en aquest moment. Torna-ho a provar d'aquí a uns segons.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-client-key": "own:appointment"
      }
    },
    "/api/v1/catalog/services": {
      "get": {
        "operationId": "listServices",
        "tags": [
          "Catàleg"
        ],
        "summary": "Categories i subcategories de servei",
        "description": "Permís necessari: `catalog:read`.\n\nClau de client: permès (no porta dades d'altres clients).",
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ],
        "x-scope": "catalog:read",
        "responses": {
          "200": {
            "description": "Resposta correcta",
            "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": "Error — bad_request: La petició no és vàlida.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Error — missing_api_key: Falta la clau API: envia-la a la capçalera Authorization: Bearer pt_… o X-Api-Key. | invalid_api_key: La clau API no és vàlida per a aquesta instància.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Error — ip_not_allowed: L'adreça IP d'origen no és a la llista permesa d'aquesta clau. | insufficient_scope: La clau API no té el permís necessari per a aquesta operació. | plan_scope_not_allowed: El pla d'API del taller no inclou aquest permís. Per fer-lo servir cal millorar el pla.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Error — rate_limited: Has superat el límit de peticions d'aquesta clau. Espera i torna-ho a provar. | plan_quota_exceeded: S'ha esgotat la quota diària de peticions del pla d'API del taller. Es renova a les 00:00 UTC; per a més volum, millora el pla.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Error — internal_error: Error intern.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Error — instance_rate_limited: La instància està rebent massa peticions per API en aquest moment. Torna-ho a provar d'aquí a uns segons.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-client-key": "open"
      }
    },
    "/api/v1/catalog/rates": {
      "get": {
        "operationId": "listRates",
        "tags": [
          "Catàleg"
        ],
        "summary": "Tarifes de mà d'obra",
        "description": "Només el preu de venda per hora; el cost mai no surt per l'API.\n\nPermís necessari: `catalog:read`.\n\nClau de client: permès (no porta dades d'altres clients).",
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ],
        "x-scope": "catalog:read",
        "responses": {
          "200": {
            "description": "Resposta correcta",
            "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": "Error — bad_request: La petició no és vàlida.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Error — missing_api_key: Falta la clau API: envia-la a la capçalera Authorization: Bearer pt_… o X-Api-Key. | invalid_api_key: La clau API no és vàlida per a aquesta instància.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Error — ip_not_allowed: L'adreça IP d'origen no és a la llista permesa d'aquesta clau. | insufficient_scope: La clau API no té el permís necessari per a aquesta operació. | plan_scope_not_allowed: El pla d'API del taller no inclou aquest permís. Per fer-lo servir cal millorar el pla.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Error — rate_limited: Has superat el límit de peticions d'aquesta clau. Espera i torna-ho a provar. | plan_quota_exceeded: S'ha esgotat la quota diària de peticions del pla d'API del taller. Es renova a les 00:00 UTC; per a més volum, millora el pla.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Error — internal_error: Error intern.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Error — instance_rate_limited: La instància està rebent massa peticions per API en aquest moment. Torna-ho a provar d'aquí a uns segons.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-client-key": "open"
      }
    },
    "/api/v1/stock": {
      "get": {
        "operationId": "listStock",
        "tags": [
          "Recanvis"
        ],
        "summary": "Llistar recanvis de l'estoc",
        "description": "Existències amb unitat i decimals (quantity, reserved, pending, planned, available, min_stock), referències i preu de venda. Disponible = quantitat − bloquejat per pressupostos acceptats o en curs; el previst en pressupostos oberts no bloqueja. El cost de compra, el proveïdor i l'historial de compres no surten mai per l'API. Per defecte només peces actives.\n\nPermís necessari: `stock:read`.\n\nClau de client: permès (no porta dades d'altres clients).",
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ],
        "x-scope": "stock:read",
        "parameters": [
          {
            "name": "search",
            "in": "query",
            "required": false,
            "description": "Cerca al nom, la marca, les referències i l'EAN (les referències sense tenir en compte espais, punts ni guions).",
            "schema": {
              "type": "string",
              "example": "turbo"
            }
          },
          {
            "name": "reference",
            "in": "query",
            "required": false,
            "description": "Referència o EAN exactes, sense tenir en compte espais, punts ni guions.",
            "schema": {
              "type": "string",
              "example": "A6540907801"
            }
          },
          {
            "name": "in_stock",
            "in": "query",
            "required": false,
            "description": "true: només amb quantitat al magatzem; false: només amb quantitat zero (mira available per al que és lliure).",
            "schema": {
              "type": "boolean"
            }
          },
          {
            "name": "include_inactive",
            "in": "query",
            "required": false,
            "description": "Incloure peces donades de baixa de l'inventari.",
            "schema": {
              "type": "boolean",
              "default": "false"
            }
          },
          {
            "name": "updated_since",
            "in": "query",
            "required": false,
            "description": "Només registres modificats a partir d'aquesta data (ISO 8601).",
            "schema": {
              "type": "string",
              "format": "date-time",
              "example": "2026-10-01T00:00:00Z"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Resultats per pàgina (1–200).",
            "schema": {
              "type": "integer",
              "default": 50,
              "example": 50
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "required": false,
            "description": "Cursor opac retornat a next_cursor de la pàgina anterior.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Resposta correcta",
            "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/StockItem"
                      }
                    },
                    "next_cursor": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "description": "Cursor opac per demanar la pàgina següent; null si no n'hi ha més",
                      "example": "aWQ6MTIzNA"
                    },
                    "has_more": {
                      "type": "boolean",
                      "description": "true si queden més resultats",
                      "example": true
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Error — bad_request: La petició no és vàlida.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Error — missing_api_key: Falta la clau API: envia-la a la capçalera Authorization: Bearer pt_… o X-Api-Key. | invalid_api_key: La clau API no és vàlida per a aquesta instància.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Error — ip_not_allowed: L'adreça IP d'origen no és a la llista permesa d'aquesta clau. | insufficient_scope: La clau API no té el permís necessari per a aquesta operació. | plan_scope_not_allowed: El pla d'API del taller no inclou aquest permís. Per fer-lo servir cal millorar el pla.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Error — rate_limited: Has superat el límit de peticions d'aquesta clau. Espera i torna-ho a provar. | plan_quota_exceeded: S'ha esgotat la quota diària de peticions del pla d'API del taller. Es renova a les 00:00 UTC; per a més volum, millora el pla.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Error — internal_error: Error intern.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Error — instance_rate_limited: La instància està rebent massa peticions per API en aquest moment. Torna-ho a provar d'aquí a uns segons.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-client-key": "open"
      }
    },
    "/api/v1/stock/{id}": {
      "get": {
        "operationId": "getStockItem",
        "tags": [
          "Recanvis"
        ],
        "summary": "Detall d'un recanvi",
        "description": "Permís necessari: `stock:read`.\n\nClau de client: permès (no porta dades d'altres clients).",
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ],
        "x-scope": "stock:read",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Id del recanvi.",
            "schema": {
              "type": "integer",
              "example": 1234
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Resposta correcta",
            "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/StockItem"
                }
              }
            }
          },
          "400": {
            "description": "Error — bad_request: La petició no és vàlida.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Error — missing_api_key: Falta la clau API: envia-la a la capçalera Authorization: Bearer pt_… o X-Api-Key. | invalid_api_key: La clau API no és vàlida per a aquesta instància.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Error — ip_not_allowed: L'adreça IP d'origen no és a la llista permesa d'aquesta clau. | insufficient_scope: La clau API no té el permís necessari per a aquesta operació. | plan_scope_not_allowed: El pla d'API del taller no inclou aquest permís. Per fer-lo servir cal millorar el pla.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Error — not_found: No s'ha trobat el recurs.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Error — rate_limited: Has superat el límit de peticions d'aquesta clau. Espera i torna-ho a provar. | plan_quota_exceeded: S'ha esgotat la quota diària de peticions del pla d'API del taller. Es renova a les 00:00 UTC; per a més volum, millora el pla.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Error — internal_error: Error intern.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Error — instance_rate_limited: La instància està rebent massa peticions per API en aquest moment. Torna-ho a provar d'aquí a uns segons.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-client-key": "open"
      }
    },
    "/api/v1/communications": {
      "get": {
        "operationId": "listCommunications",
        "tags": [
          "Comunicacions"
        ],
        "summary": "Registre de comunicacions",
        "description": "Últimes comunicacions (email, SMS, WhatsApp, trucades, push) amb el contingut complet, de la més recent a la més antiga.\n\nPermís necessari: `communications:read`.\n\nClau de client: només retorna o crea dades del seu client.",
        "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àxim de resultats (1–200).",
            "schema": {
              "type": "integer",
              "default": 50
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Resposta correcta",
            "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": "Error — bad_request: La petició no és vàlida.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Error — missing_api_key: Falta la clau API: envia-la a la capçalera Authorization: Bearer pt_… o X-Api-Key. | invalid_api_key: La clau API no és vàlida per a aquesta instància.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Error — ip_not_allowed: L'adreça IP d'origen no és a la llista permesa d'aquesta clau. | insufficient_scope: La clau API no té el permís necessari per a aquesta operació. | plan_scope_not_allowed: El pla d'API del taller no inclou aquest permís. Per fer-lo servir cal millorar el pla.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Error — rate_limited: Has superat el límit de peticions d'aquesta clau. Espera i torna-ho a provar. | plan_quota_exceeded: S'ha esgotat la quota diària de peticions del pla d'API del taller. Es renova a les 00:00 UTC; per a més volum, millora el pla.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Error — internal_error: Error intern.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Error — instance_rate_limited: La instància està rebent massa peticions per API en aquest moment. Torna-ho a provar d'aquí a uns segons.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-client-key": "filtered"
      }
    },
    "/api/v1/logs": {
      "get": {
        "operationId": "listLogs",
        "tags": [
          "Comunicacions"
        ],
        "summary": "Registre de comunicacions (àlies antic)",
        "description": "Mateixa consulta que /communications però retorna { items }. Es manté per compatibilitat; fes servir /communications.\n\nPermís necessari: `communications:read`.\n\nClau de client: només retorna o crea dades del seu client.",
        "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àxim de resultats (1–200).",
            "schema": {
              "type": "integer",
              "default": 50
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Resposta correcta",
            "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": "Error — bad_request: La petició no és vàlida.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Error — missing_api_key: Falta la clau API: envia-la a la capçalera Authorization: Bearer pt_… o X-Api-Key. | invalid_api_key: La clau API no és vàlida per a aquesta instància.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Error — ip_not_allowed: L'adreça IP d'origen no és a la llista permesa d'aquesta clau. | insufficient_scope: La clau API no té el permís necessari per a aquesta operació. | plan_scope_not_allowed: El pla d'API del taller no inclou aquest permís. Per fer-lo servir cal millorar el pla.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Error — rate_limited: Has superat el límit de peticions d'aquesta clau. Espera i torna-ho a provar. | plan_quota_exceeded: S'ha esgotat la quota diària de peticions del pla d'API del taller. Es renova a les 00:00 UTC; per a més volum, millora el pla.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Error — internal_error: Error intern.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Error — instance_rate_limited: La instància està rebent massa peticions per API en aquest moment. Torna-ho a provar d'aquí a uns segons.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-client-key": "filtered"
      }
    },
    "/api/v1/email": {
      "post": {
        "operationId": "sendEmail",
        "tags": [
          "Comunicacions"
        ],
        "summary": "Enviar un email transaccional",
        "description": "Només text: surt amb la plantilla, el nom i el compte de correu del taller, i queda al registre de Comunicació. No admet HTML ni remitent propi, i replyTo només pot ser una adreça del domini del taller. Si rebota, l'email del client es marca com a no vàlid.\n\nPermís necessari: `communications:send`.\n\nClau de client: no disponible (403 client_key_forbidden).",
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ],
        "x-scope": "communications:send",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "to",
                  "subject",
                  "text"
                ],
                "properties": {
                  "to": {
                    "type": "string",
                    "format": "email",
                    "example": "cliente@ejemplo.com"
                  },
                  "subject": {
                    "type": "string",
                    "maxLength": 200,
                    "example": "El seu vehicle és a punt"
                  },
                  "text": {
                    "type": "string",
                    "maxLength": 20000,
                    "example": "Ja el pot passar a recollir."
                  },
                  "replyTo": {
                    "type": "string",
                    "format": "email",
                    "example": "taller@ejemplo.com"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Resposta correcta",
            "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": "Error — bad_request: La petició no és vàlida.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Error — missing_api_key: Falta la clau API: envia-la a la capçalera Authorization: Bearer pt_… o X-Api-Key. | invalid_api_key: La clau API no és vàlida per a aquesta instància.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Error — ip_not_allowed: L'adreça IP d'origen no és a la llista permesa d'aquesta clau. | insufficient_scope: La clau API no té el permís necessari per a aquesta operació. | plan_scope_not_allowed: El pla d'API del taller no inclou aquest permís. Per fer-lo servir cal millorar el pla. | test_key_forbidden: Una clau de prova (pt_test_) no pot fer operacions amb efectes: fes servir una clau live. | client_key_forbidden: Aquesta clau està limitada a un client del taller i no pot fer aquesta operació: només treballa amb les dades del seu client.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Error — rate_limited: Has superat el límit de peticions d'aquesta clau. Espera i torna-ho a provar. | plan_quota_exceeded: S'ha esgotat la quota diària de peticions del pla d'API del taller. Es renova a les 00:00 UTC; per a més volum, millora el pla.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Error — internal_error: Error intern.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Error — instance_rate_limited: La instància està rebent massa peticions per API en aquest moment. Torna-ho a provar d'aquí a uns segons.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-client-key": "deny"
      }
    },
    "/api/v1/sms": {
      "post": {
        "operationId": "sendSms",
        "tags": [
          "Comunicacions"
        ],
        "summary": "Enviar un SMS transaccional",
        "description": "Surt amb el servei d'SMS configurat al taller i queda al registre de Comunicació, on s'actualitza l'estat de lliurament.\n\nPermís necessari: `communications:send`.\n\nClau de client: no disponible (403 client_key_forbidden).",
        "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": "El seu vehicle ja es pot recollir."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Resposta correcta",
            "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": "Error — bad_request: La petició no és vàlida.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Error — missing_api_key: Falta la clau API: envia-la a la capçalera Authorization: Bearer pt_… o X-Api-Key. | invalid_api_key: La clau API no és vàlida per a aquesta instància.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Error — ip_not_allowed: L'adreça IP d'origen no és a la llista permesa d'aquesta clau. | insufficient_scope: La clau API no té el permís necessari per a aquesta operació. | plan_scope_not_allowed: El pla d'API del taller no inclou aquest permís. Per fer-lo servir cal millorar el pla. | test_key_forbidden: Una clau de prova (pt_test_) no pot fer operacions amb efectes: fes servir una clau live. | client_key_forbidden: Aquesta clau està limitada a un client del taller i no pot fer aquesta operació: només treballa amb les dades del seu client.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Error — rate_limited: Has superat el límit de peticions d'aquesta clau. Espera i torna-ho a provar. | plan_quota_exceeded: S'ha esgotat la quota diària de peticions del pla d'API del taller. Es renova a les 00:00 UTC; per a més volum, millora el pla.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Error — internal_error: Error intern.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Error — instance_rate_limited: La instància està rebent massa peticions per API en aquest moment. Torna-ho a provar d'aquí a uns segons.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-client-key": "deny"
      }
    },
    "/api/v1/conversations": {
      "get": {
        "operationId": "listConversations",
        "tags": [
          "Converses"
        ],
        "summary": "Converses de la Safata",
        "description": "WhatsApp, SMS, xat de la web i correu en una llista, com la Safata d'entrada del programa, de la més recent a la més antiga. Cadascuna amb la seva clau estable (canal:número o canal:id), client, pressupost actiu, estat (open: falta contestar; pending: esperant el client; closed) i si la finestra de 24 h de WhatsApp continua oberta.\n\nPermís necessari: `communications:read`.\n\nClau de client: només retorna o crea dades del seu client.",
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ],
        "x-scope": "communications:read",
        "parameters": [
          {
            "name": "channel",
            "in": "query",
            "required": false,
            "description": "Només un canal.",
            "schema": {
              "type": "string",
              "enum": [
                "whatsapp",
                "sms",
                "chat",
                "email"
              ]
            }
          },
          {
            "name": "status",
            "in": "query",
            "required": false,
            "description": "Només un estat.",
            "schema": {
              "type": "string",
              "enum": [
                "open",
                "pending",
                "closed"
              ]
            }
          },
          {
            "name": "days",
            "in": "query",
            "required": false,
            "description": "Activitat dels darrers N dies (1–365).",
            "schema": {
              "type": "integer",
              "default": 30
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Màxim de resultats (1–200).",
            "schema": {
              "type": "integer",
              "default": 50
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Resposta correcta",
            "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/Conversation"
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Error — bad_request: La petició no és vàlida.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Error — missing_api_key: Falta la clau API: envia-la a la capçalera Authorization: Bearer pt_… o X-Api-Key. | invalid_api_key: La clau API no és vàlida per a aquesta instància.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Error — ip_not_allowed: L'adreça IP d'origen no és a la llista permesa d'aquesta clau. | insufficient_scope: La clau API no té el permís necessari per a aquesta operació. | plan_scope_not_allowed: El pla d'API del taller no inclou aquest permís. Per fer-lo servir cal millorar el pla.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Error — rate_limited: Has superat el límit de peticions d'aquesta clau. Espera i torna-ho a provar. | plan_quota_exceeded: S'ha esgotat la quota diària de peticions del pla d'API del taller. Es renova a les 00:00 UTC; per a més volum, millora el pla.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Error — internal_error: Error intern.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Error — instance_rate_limited: La instància està rebent massa peticions per API en aquest moment. Torna-ho a provar d'aquí a uns segons.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-client-key": "filtered"
      }
    },
    "/api/v1/conversations/{key}/messages": {
      "get": {
        "operationId": "listConversationMessages",
        "tags": [
          "Converses"
        ],
        "summary": "Missatges d'una conversa",
        "description": "El fil complet en ordre cronològic, amb autor (cliente, ia, equipo, automatico o integracion), text i adjunts. Els correus es retornen com a text, sense la plantilla ni el fil citat.\n\nPermís necessari: `communications:read`.\n\nClau de client: només si el recurs és del seu client; si no, 404.",
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ],
        "x-scope": "communications:read",
        "parameters": [
          {
            "name": "key",
            "in": "path",
            "required": true,
            "description": "Clau de la conversa: whatsapp:<9 dígits>, sms:<9 dígits>, chat:<id> o email:<id>.",
            "schema": {
              "type": "string",
              "example": "whatsapp:600111222"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Resposta correcta",
            "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": [
                    "conversation",
                    "data"
                  ],
                  "additionalProperties": false,
                  "properties": {
                    "conversation": {
                      "$ref": "#/components/schemas/Conversation"
                    },
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/ConversationMessage"
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Error — bad_request: La petició no és vàlida.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Error — missing_api_key: Falta la clau API: envia-la a la capçalera Authorization: Bearer pt_… o X-Api-Key. | invalid_api_key: La clau API no és vàlida per a aquesta instància.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Error — ip_not_allowed: L'adreça IP d'origen no és a la llista permesa d'aquesta clau. | insufficient_scope: La clau API no té el permís necessari per a aquesta operació. | plan_scope_not_allowed: El pla d'API del taller no inclou aquest permís. Per fer-lo servir cal millorar el pla.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Error — conversation_not_found: No existeix aquesta conversa.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Error — rate_limited: Has superat el límit de peticions d'aquesta clau. Espera i torna-ho a provar. | plan_quota_exceeded: S'ha esgotat la quota diària de peticions del pla d'API del taller. Es renova a les 00:00 UTC; per a més volum, millora el pla.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Error — internal_error: Error intern.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Error — instance_rate_limited: La instància està rebent massa peticions per API en aquest moment. Torna-ho a provar d'aquí a uns segons.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-client-key": "own:conversation"
      },
      "post": {
        "operationId": "sendConversationMessage",
        "tags": [
          "Converses"
        ],
        "summary": "Respondre en una conversa",
        "description": "Surt pel mateix canal i amb les mateixes funcions que la Safata d'entrada del programa. WhatsApp: text i/o media_url dins de la finestra de 24 h; fora d'ella només amb template_id (plantilla aprovada; si no, 422 whatsapp_window_closed). SMS: text (un fragment). Xat web: text. Correu: subject i text. El missatge queda registrat amb la integració com a autora («API · <clau>»), marca la conversa com a contestada i, si hi havia un missatge esperant l'assistent extern, cancel·la la resposta de reserva de l'assistent del programa. A l'entorn de proves res no surt de debò (simulated=true).\n\nPermís necessari: `messages:send`.\n\nClau de client: només si el recurs és del seu client; si no, 404.",
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ],
        "x-scope": "messages:send",
        "parameters": [
          {
            "name": "key",
            "in": "path",
            "required": true,
            "description": "Clau de la conversa: whatsapp:<9 dígits>, sms:<9 dígits>, chat:<id> o email:<id>.",
            "schema": {
              "type": "string",
              "example": "whatsapp:600111222"
            }
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": true,
            "description": "Clau única per operació (es recomana un UUID). Repetir-la amb el mateix cos en 24 h retorna la resposta desada amb Idempotent-Replay: true; amb un altre cos, 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": {
                  "text": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 4000,
                    "description": "Text del missatge. WhatsApp: màxim 1.600 caràcters; SMS: un fragment, com al programa",
                    "example": "El seu cotxe estarà a punt a les 18:00."
                  },
                  "media_url": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 1000,
                    "description": "Només WhatsApp: URL https pública d'una imatge o PDF",
                    "example": null,
                    "pattern": "^https://"
                  },
                  "template_id": {
                    "type": "integer",
                    "minimum": 1,
                    "description": "Només WhatsApp: plantilla aprovada (GET /messages/templates). Obligatòria fora de la finestra de 24 h",
                    "example": 12
                  },
                  "template_variables": {
                    "type": "object",
                    "additionalProperties": {
                      "type": "string",
                      "maxLength": 500
                    },
                    "description": "Valors de les etiquetes de la plantilla, p. ex. {\"#nombre#\": \"Laura\"}",
                    "example": {
                      "#nombre#": "Laura"
                    }
                  },
                  "subject": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 200,
                    "description": "Només correu: assumpte (obligatori al correu)",
                    "example": "El seu pressupost"
                  }
                },
                "required": [],
                "additionalProperties": false,
                "description": "Resposta en una conversa"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Resposta correcta",
            "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/MessageSent"
                }
              }
            }
          },
          "400": {
            "description": "Error — bad_request: La petició no és vàlida. | idempotency_key_required: Falta la capçalera Idempotency-Key (obligatòria a tot POST; de 8 a 255 caràcters visibles).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Error — missing_api_key: Falta la clau API: envia-la a la capçalera Authorization: Bearer pt_… o X-Api-Key. | invalid_api_key: La clau API no és vàlida per a aquesta instància.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Error — ip_not_allowed: L'adreça IP d'origen no és a la llista permesa d'aquesta clau. | insufficient_scope: La clau API no té el permís necessari per a aquesta operació. | plan_scope_not_allowed: El pla d'API del taller no inclou aquest permís. Per fer-lo servir cal millorar el pla. | test_key_forbidden: Una clau de prova (pt_test_) no pot fer operacions amb efectes: fes servir una clau live.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Error — conversation_not_found: No existeix aquesta conversa.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Error — channel_unavailable: Aquest canal no està configurat al taller. | idempotency_conflict: Aquesta Idempotency-Key ja s'ha fet servir les últimes 24 h amb una altra petició. | idempotency_in_progress: Hi ha una altra petició amb la mateixa Idempotency-Key en curs. Torna-ho a provar d'aquí a uns segons.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "Error — validation_error: El cos de la petició no és vàlid: revisa la llista «fields». | whatsapp_window_closed: Han passat més de 24 h des de l'últim missatge del client: per WhatsApp només se li pot escriure amb una plantilla aprovada (template_id).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Error — rate_limited: Has superat el límit de peticions d'aquesta clau. Espera i torna-ho a provar. | plan_quota_exceeded: S'ha esgotat la quota diària de peticions del pla d'API del taller. Es renova a les 00:00 UTC; per a més volum, millora el pla.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Error — internal_error: Error intern.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "502": {
            "description": "Error — upstream_error: Un servei extern ha rebutjat l'operació.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Error — instance_rate_limited: La instància està rebent massa peticions per API en aquest moment. Torna-ho a provar d'aquí a uns segons.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-client-key": "own:conversation"
      }
    },
    "/api/v1/messages/templates": {
      "get": {
        "operationId": "listWhatsAppTemplates",
        "tags": [
          "Converses"
        ],
        "summary": "Plantilles de WhatsApp aprovades",
        "description": "Les plantilles que Meta ha aprovat al taller: el seu id és el template_id per escriure fora de la finestra de 24 h.\n\nPermís necessari: `communications:read`.\n\nClau de client: permès (no porta dades d'altres clients).",
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ],
        "x-scope": "communications:read",
        "responses": {
          "200": {
            "description": "Resposta correcta",
            "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/WhatsAppTemplate"
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Error — bad_request: La petició no és vàlida.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Error — missing_api_key: Falta la clau API: envia-la a la capçalera Authorization: Bearer pt_… o X-Api-Key. | invalid_api_key: La clau API no és vàlida per a aquesta instància.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Error — ip_not_allowed: L'adreça IP d'origen no és a la llista permesa d'aquesta clau. | insufficient_scope: La clau API no té el permís necessari per a aquesta operació. | plan_scope_not_allowed: El pla d'API del taller no inclou aquest permís. Per fer-lo servir cal millorar el pla.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Error — rate_limited: Has superat el límit de peticions d'aquesta clau. Espera i torna-ho a provar. | plan_quota_exceeded: S'ha esgotat la quota diària de peticions del pla d'API del taller. Es renova a les 00:00 UTC; per a més volum, millora el pla.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Error — internal_error: Error intern.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Error — instance_rate_limited: La instància està rebent massa peticions per API en aquest moment. Torna-ho a provar d'aquí a uns segons.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-client-key": "open"
      }
    },
    "/api/v1/settings/assistant": {
      "get": {
        "operationId": "getAssistantSettings",
        "tags": [
          "Ajustos"
        ],
        "summary": "Mode assistent extern",
        "description": "En quins canals contesta el teu assistent en lloc de l'assistent del programa, la clau que els atén, els minuts de reserva i els canals que de debò atens ara (clau viva i webhook message.received actiu).\n\nPermís necessari: `settings:write`.\n\nClau de client: no disponible (403 client_key_forbidden).",
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ],
        "x-scope": "settings:write",
        "responses": {
          "200": {
            "description": "Resposta correcta",
            "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/AssistantSettings"
                }
              }
            }
          },
          "400": {
            "description": "Error — bad_request: La petició no és vàlida.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Error — missing_api_key: Falta la clau API: envia-la a la capçalera Authorization: Bearer pt_… o X-Api-Key. | invalid_api_key: La clau API no és vàlida per a aquesta instància.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Error — ip_not_allowed: L'adreça IP d'origen no és a la llista permesa d'aquesta clau. | insufficient_scope: La clau API no té el permís necessari per a aquesta operació. | plan_scope_not_allowed: El pla d'API del taller no inclou aquest permís. Per fer-lo servir cal millorar el pla. | test_key_forbidden: Una clau de prova (pt_test_) no pot fer operacions amb efectes: fes servir una clau live. | client_key_forbidden: Aquesta clau està limitada a un client del taller i no pot fer aquesta operació: només treballa amb les dades del seu client.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Error — rate_limited: Has superat el límit de peticions d'aquesta clau. Espera i torna-ho a provar. | plan_quota_exceeded: S'ha esgotat la quota diària de peticions del pla d'API del taller. Es renova a les 00:00 UTC; per a més volum, millora el pla.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Error — internal_error: Error intern.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Error — instance_rate_limited: La instància està rebent massa peticions per API en aquest moment. Torna-ho a provar d'aquí a uns segons.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-client-key": "deny"
      },
      "put": {
        "operationId": "updateAssistantSettings",
        "tags": [
          "Ajustos"
        ],
        "summary": "Canviar el mode assistent extern",
        "description": "Als canals d'external_channels l'assistent del programa deixa de contestar: cada missatge entra, es registra, s'emet message.received i s'espera la teva resposta (POST /conversations/{key}/messages). Salvaguarda: si no respons en fallback_minutes (10 per defecte), si la clau es revoca o caduca, o si no hi ha cap webhook message.received actiu o el seu lliurament falla, contesta l'assistent del programa a aquest missatge i queda registrat. La clau que fa la petició passa a ser la que atén. external_channels=[] ho torna tot al programa.\n\nPermís necessari: `settings:write`.\n\nClau de client: no disponible (403 client_key_forbidden).",
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ],
        "x-scope": "settings:write",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Clau única per operació (es recomana un UUID). Repetir-la amb el mateix cos en 24 h retorna la resposta desada amb Idempotent-Replay: true; amb un altre cos, 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": {
                  "external_channels": {
                    "type": "array",
                    "maxItems": 4,
                    "items": {
                      "type": "string",
                      "enum": [
                        "whatsapp",
                        "sms",
                        "chat",
                        "email"
                      ]
                    },
                    "description": "Canals en què contesta el teu assistent; [] els torna tots a l'assistent del programa",
                    "example": [
                      "whatsapp"
                    ]
                  },
                  "fallback_minutes": {
                    "type": "integer",
                    "minimum": 1,
                    "maximum": 120,
                    "description": "Si la teva integració no respon en aquests minuts, contesta l'assistent del programa",
                    "example": 10
                  }
                },
                "required": [],
                "additionalProperties": false,
                "description": "Mode assistent extern (la clau que el canvia queda com la que atén)"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Resposta correcta",
            "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/AssistantSettings"
                }
              }
            }
          },
          "400": {
            "description": "Error — bad_request: La petició no és vàlida.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Error — missing_api_key: Falta la clau API: envia-la a la capçalera Authorization: Bearer pt_… o X-Api-Key. | invalid_api_key: La clau API no és vàlida per a aquesta instància.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Error — ip_not_allowed: L'adreça IP d'origen no és a la llista permesa d'aquesta clau. | insufficient_scope: La clau API no té el permís necessari per a aquesta operació. | plan_scope_not_allowed: El pla d'API del taller no inclou aquest permís. Per fer-lo servir cal millorar el pla. | test_key_forbidden: Una clau de prova (pt_test_) no pot fer operacions amb efectes: fes servir una clau live. | client_key_forbidden: Aquesta clau està limitada a un client del taller i no pot fer aquesta operació: només treballa amb les dades del seu client.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "Error — validation_error: El cos de la petició no és vàlid: revisa la llista «fields».",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ValidationError"
                }
              }
            }
          },
          "429": {
            "description": "Error — rate_limited: Has superat el límit de peticions d'aquesta clau. Espera i torna-ho a provar. | plan_quota_exceeded: S'ha esgotat la quota diària de peticions del pla d'API del taller. Es renova a les 00:00 UTC; per a més volum, millora el pla.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Error — internal_error: Error intern.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Error — instance_rate_limited: La instància està rebent massa peticions per API en aquest moment. Torna-ho a provar d'aquí a uns segons.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-client-key": "deny"
      }
    },
    "/api/v1/sandbox/keys": {
      "post": {
        "operationId": "createSandboxKey",
        "tags": [
          "Entorn de proves"
        ],
        "summary": "Demanar una clau de proves",
        "description": "Només a https://demo.programa-taller.es (a les altres instàncies respon 404 sandbox_only). Sense autenticació ni cost: retorna una clau pt_test_… amb tots els permisos, vàlida 30 dies, que només es mostra una vegada. Màxim 3 al dia per IP i per correu. Les dades de la demo són fictícies i es restauren cada 2 hores; les claus, els webhooks i el registre de peticions es conserven. Res no surt de debò: els enviaments es registren com a simulats i els webhooks s'emeten igualment.\n\nSense autenticació.",
        "security": [],
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Clau única per operació (es recomana un UUID). Repetir-la amb el mateix cos en 24 h retorna la resposta desada amb Idempotent-Replay: true; amb un altre cos, 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": {
                  "email": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 254,
                    "description": "Correu de contacte de l'integrador",
                    "example": "dev@ejemplo.com",
                    "format": "email"
                  },
                  "company": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 120,
                    "example": "Integracions Exemple SL"
                  },
                  "name": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 120,
                    "example": "Anna Puig"
                  }
                },
                "required": [
                  "email",
                  "company",
                  "name"
                ],
                "additionalProperties": false,
                "description": "Alta d'una clau de proves"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Resposta correcta",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SandboxKey"
                }
              }
            }
          },
          "400": {
            "description": "Error — bad_request: La petició no és vàlida.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Error — sandbox_only: Només disponible a l'entorn de proves (https://demo.programa-taller.es).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "Error — validation_error: El cos de la petició no és vàlid: revisa la llista «fields».",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ValidationError"
                }
              }
            }
          },
          "429": {
            "description": "Error — rate_limited: Has superat el límit de peticions d'aquesta clau. Espera i torna-ho a provar.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Error — internal_error: Error intern.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Error — instance_rate_limited: La instància està rebent massa peticions per API en aquest moment. Torna-ho a provar d'aquí a uns segons.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/sandbox/simulate/message": {
      "post": {
        "operationId": "simulateInboundMessage",
        "tags": [
          "Entorn de proves"
        ],
        "summary": "Simular un missatge entrant",
        "description": "Només a l'entorn de proves. Crea un missatge entrant fictici per WhatsApp, SMS, xat web o correu, com si l'hagués escrit un client: entra a la Safata d'entrada, s'enllaça amb el client si el telèfon o el correu coincideixen i dispara message.received per provar el teu bot de punta a punta. Contesta amb POST /conversations/{key}/messages (la resposta es simula).\n\nQualsevol clau vàlida, sense cap permís concret.\n\nClau de client: no disponible (403 client_key_forbidden).",
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": true,
            "description": "Clau única per operació (es recomana un UUID). Repetir-la amb el mateix cos en 24 h retorna la resposta desada amb Idempotent-Replay: true; amb un altre cos, 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": {
                  "channel": {
                    "type": "string",
                    "enum": [
                      "whatsapp",
                      "sms",
                      "chat",
                      "email"
                    ],
                    "example": "whatsapp"
                  },
                  "from": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 254,
                    "description": "Telèfon (whatsapp/sms), correu (email) o nom del visitant (chat)",
                    "example": "+34600111222"
                  },
                  "text": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 4000,
                    "description": "Text del missatge",
                    "example": "Hola, teniu lloc demà per canviar l'oli?"
                  },
                  "subject": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 200,
                    "description": "Només correu",
                    "example": "Consulta"
                  },
                  "media_urls": {
                    "type": "array",
                    "maxItems": 5,
                    "items": {
                      "type": "string",
                      "pattern": "^https://",
                      "maxLength": 1000
                    },
                    "description": "Adjunts (URLs https)",
                    "example": []
                  }
                },
                "required": [
                  "channel",
                  "from",
                  "text"
                ],
                "additionalProperties": false,
                "description": "Missatge entrant fictici (només entorn de proves)"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Resposta correcta",
            "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": [
                    "ok",
                    "key"
                  ],
                  "additionalProperties": false,
                  "properties": {
                    "ok": {
                      "type": "boolean",
                      "example": true
                    },
                    "key": {
                      "type": "string",
                      "example": "whatsapp:600111222"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Error — bad_request: La petició no és vàlida. | idempotency_key_required: Falta la capçalera Idempotency-Key (obligatòria a tot POST; de 8 a 255 caràcters visibles).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Error — missing_api_key: Falta la clau API: envia-la a la capçalera Authorization: Bearer pt_… o X-Api-Key. | invalid_api_key: La clau API no és vàlida per a aquesta instància.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Error — ip_not_allowed: L'adreça IP d'origen no és a la llista permesa d'aquesta clau. | client_key_forbidden: Aquesta clau està limitada a un client del taller i no pot fer aquesta operació: només treballa amb les dades del seu client.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Error — sandbox_only: Només disponible a l'entorn de proves (https://demo.programa-taller.es).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Error — idempotency_conflict: Aquesta Idempotency-Key ja s'ha fet servir les últimes 24 h amb una altra petició. | idempotency_in_progress: Hi ha una altra petició amb la mateixa Idempotency-Key en curs. Torna-ho a provar d'aquí a uns segons.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "Error — validation_error: El cos de la petició no és vàlid: revisa la llista «fields».",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ValidationError"
                }
              }
            }
          },
          "429": {
            "description": "Error — rate_limited: Has superat el límit de peticions d'aquesta clau. Espera i torna-ho a provar. | plan_quota_exceeded: S'ha esgotat la quota diària de peticions del pla d'API del taller. Es renova a les 00:00 UTC; per a més volum, millora el pla.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Error — internal_error: Error intern.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Error — instance_rate_limited: La instància està rebent massa peticions per API en aquest moment. Torna-ho a provar d'aquí a uns segons.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-client-key": "deny"
      }
    },
    "/api/v1/webhooks": {
      "get": {
        "operationId": "listWebhooks",
        "tags": [
          "Webhooks"
        ],
        "summary": "Llistar els webhooks de la clau",
        "description": "Inclou el catàleg d'esdeveniments disponibles. Mai no retorna els secrets.\n\nPermís necessari: `webhooks:manage`.\n\nClau de client: permès (no porta dades d'altres clients).",
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ],
        "x-scope": "webhooks:manage",
        "responses": {
          "200": {
            "description": "Resposta correcta",
            "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": "Pressupost acceptat"
                          },
                          "description": {
                            "type": "string"
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Error — bad_request: La petició no és vàlida.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Error — missing_api_key: Falta la clau API: envia-la a la capçalera Authorization: Bearer pt_… o X-Api-Key. | invalid_api_key: La clau API no és vàlida per a aquesta instància.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Error — ip_not_allowed: L'adreça IP d'origen no és a la llista permesa d'aquesta clau. | insufficient_scope: La clau API no té el permís necessari per a aquesta operació. | plan_scope_not_allowed: El pla d'API del taller no inclou aquest permís. Per fer-lo servir cal millorar el pla. | test_key_forbidden: Una clau de prova (pt_test_) no pot fer operacions amb efectes: fes servir una clau live.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Error — rate_limited: Has superat el límit de peticions d'aquesta clau. Espera i torna-ho a provar. | plan_quota_exceeded: S'ha esgotat la quota diària de peticions del pla d'API del taller. Es renova a les 00:00 UTC; per a més volum, millora el pla.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Error — internal_error: Error intern.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Error — instance_rate_limited: La instància està rebent massa peticions per API en aquest moment. Torna-ho a provar d'aquí a uns segons.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-client-key": "open"
      },
      "post": {
        "operationId": "createWebhook",
        "tags": [
          "Webhooks"
        ],
        "summary": "Crear un webhook",
        "description": "El secret de signatura (whsec_…) només viatja en aquesta resposta. Amb una clau de client només s'entreguen els esdeveniments d'aquest client (els que no pertanyen a cap client, com campaign.finished, no arriben).\n\nPermís necessari: `webhooks:manage`.\n\nClau de client: permès (no porta dades d'altres clients).",
        "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 correcta",
            "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": "Error — bad_request: La petició no és vàlida.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Error — missing_api_key: Falta la clau API: envia-la a la capçalera Authorization: Bearer pt_… o X-Api-Key. | invalid_api_key: La clau API no és vàlida per a aquesta instància.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Error — ip_not_allowed: L'adreça IP d'origen no és a la llista permesa d'aquesta clau. | insufficient_scope: La clau API no té el permís necessari per a aquesta operació. | plan_scope_not_allowed: El pla d'API del taller no inclou aquest permís. Per fer-lo servir cal millorar el pla. | test_key_forbidden: Una clau de prova (pt_test_) no pot fer operacions amb efectes: fes servir una clau live.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Error — rate_limited: Has superat el límit de peticions d'aquesta clau. Espera i torna-ho a provar. | plan_quota_exceeded: S'ha esgotat la quota diària de peticions del pla d'API del taller. Es renova a les 00:00 UTC; per a més volum, millora el pla.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Error — internal_error: Error intern.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Error — instance_rate_limited: La instància està rebent massa peticions per API en aquest moment. Torna-ho a provar d'aquí a uns segons.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-client-key": "open"
      }
    },
    "/api/v1/webhooks/{id}": {
      "get": {
        "operationId": "getWebhook",
        "tags": [
          "Webhooks"
        ],
        "summary": "Detall d'un webhook i les darreres entregues",
        "description": "Permís necessari: `webhooks:manage`.\n\nClau de client: permès (no porta dades d'altres clients).",
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ],
        "x-scope": "webhooks:manage",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Id del webhook.",
            "schema": {
              "type": "integer",
              "example": 1234
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Resposta correcta",
            "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": "Error — bad_request: La petició no és vàlida.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Error — missing_api_key: Falta la clau API: envia-la a la capçalera Authorization: Bearer pt_… o X-Api-Key. | invalid_api_key: La clau API no és vàlida per a aquesta instància.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Error — ip_not_allowed: L'adreça IP d'origen no és a la llista permesa d'aquesta clau. | insufficient_scope: La clau API no té el permís necessari per a aquesta operació. | plan_scope_not_allowed: El pla d'API del taller no inclou aquest permís. Per fer-lo servir cal millorar el pla. | test_key_forbidden: Una clau de prova (pt_test_) no pot fer operacions amb efectes: fes servir una clau live.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Error — not_found: No s'ha trobat el recurs.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Error — rate_limited: Has superat el límit de peticions d'aquesta clau. Espera i torna-ho a provar. | plan_quota_exceeded: S'ha esgotat la quota diària de peticions del pla d'API del taller. Es renova a les 00:00 UTC; per a més volum, millora el pla.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Error — internal_error: Error intern.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Error — instance_rate_limited: La instància està rebent massa peticions per API en aquest moment. Torna-ho a provar d'aquí a uns segons.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-client-key": "open"
      },
      "patch": {
        "operationId": "updateWebhook",
        "tags": [
          "Webhooks"
        ],
        "summary": "Modificar un webhook",
        "description": "Permís necessari: `webhooks:manage`.\n\nClau de client: permès (no porta dades d'altres clients).",
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ],
        "x-scope": "webhooks:manage",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Id del 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 correcta",
            "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": "Error — bad_request: La petició no és vàlida.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Error — missing_api_key: Falta la clau API: envia-la a la capçalera Authorization: Bearer pt_… o X-Api-Key. | invalid_api_key: La clau API no és vàlida per a aquesta instància.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Error — ip_not_allowed: L'adreça IP d'origen no és a la llista permesa d'aquesta clau. | insufficient_scope: La clau API no té el permís necessari per a aquesta operació. | plan_scope_not_allowed: El pla d'API del taller no inclou aquest permís. Per fer-lo servir cal millorar el pla. | test_key_forbidden: Una clau de prova (pt_test_) no pot fer operacions amb efectes: fes servir una clau live.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Error — not_found: No s'ha trobat el recurs.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Error — rate_limited: Has superat el límit de peticions d'aquesta clau. Espera i torna-ho a provar. | plan_quota_exceeded: S'ha esgotat la quota diària de peticions del pla d'API del taller. Es renova a les 00:00 UTC; per a més volum, millora el pla.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Error — internal_error: Error intern.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Error — instance_rate_limited: La instància està rebent massa peticions per API en aquest moment. Torna-ho a provar d'aquí a uns segons.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-client-key": "open"
      },
      "delete": {
        "operationId": "deleteWebhook",
        "tags": [
          "Webhooks"
        ],
        "summary": "Esborrar un webhook",
        "description": "Permís necessari: `webhooks:manage`.\n\nClau de client: permès (no porta dades d'altres clients).",
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ],
        "x-scope": "webhooks:manage",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Id del webhook.",
            "schema": {
              "type": "integer",
              "example": 1234
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Resposta correcta",
            "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": "Error — bad_request: La petició no és vàlida.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Error — missing_api_key: Falta la clau API: envia-la a la capçalera Authorization: Bearer pt_… o X-Api-Key. | invalid_api_key: La clau API no és vàlida per a aquesta instància.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Error — ip_not_allowed: L'adreça IP d'origen no és a la llista permesa d'aquesta clau. | insufficient_scope: La clau API no té el permís necessari per a aquesta operació. | plan_scope_not_allowed: El pla d'API del taller no inclou aquest permís. Per fer-lo servir cal millorar el pla. | test_key_forbidden: Una clau de prova (pt_test_) no pot fer operacions amb efectes: fes servir una clau live.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Error — not_found: No s'ha trobat el recurs.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Error — rate_limited: Has superat el límit de peticions d'aquesta clau. Espera i torna-ho a provar. | plan_quota_exceeded: S'ha esgotat la quota diària de peticions del pla d'API del taller. Es renova a les 00:00 UTC; per a més volum, millora el pla.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Error — internal_error: Error intern.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Error — instance_rate_limited: La instància està rebent massa peticions per API en aquest moment. Torna-ho a provar d'aquí a uns segons.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-client-key": "open"
      }
    },
    "/api/v1/webhooks/{id}/test": {
      "post": {
        "operationId": "testWebhook",
        "tags": [
          "Webhooks"
        ],
        "summary": "Enviar una entrega de prova (test.ping) al webhook",
        "description": "Permís necessari: `webhooks:manage`.\n\nClau de client: permès (no porta dades d'altres clients).",
        "security": [
          {
            "bearerAuth": []
          },
          {
            "apiKeyAuth": []
          }
        ],
        "x-scope": "webhooks:manage",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Id del webhook.",
            "schema": {
              "type": "integer",
              "example": 1234
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Resposta correcta",
            "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": "Error — bad_request: La petició no és vàlida.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Error — missing_api_key: Falta la clau API: envia-la a la capçalera Authorization: Bearer pt_… o X-Api-Key. | invalid_api_key: La clau API no és vàlida per a aquesta instància.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Error — ip_not_allowed: L'adreça IP d'origen no és a la llista permesa d'aquesta clau. | insufficient_scope: La clau API no té el permís necessari per a aquesta operació. | plan_scope_not_allowed: El pla d'API del taller no inclou aquest permís. Per fer-lo servir cal millorar el pla. | test_key_forbidden: Una clau de prova (pt_test_) no pot fer operacions amb efectes: fes servir una clau live.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Error — not_found: No s'ha trobat el recurs.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Error — rate_limited: Has superat el límit de peticions d'aquesta clau. Espera i torna-ho a provar. | plan_quota_exceeded: S'ha esgotat la quota diària de peticions del pla d'API del taller. Es renova a les 00:00 UTC; per a més volum, millora el pla.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Error — internal_error: Error intern.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Error — instance_rate_limited: La instància està rebent massa peticions per API en aquest moment. Torna-ho a provar d'aquí a uns segons.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-client-key": "open"
      }
    }
  },
  "webhooks": {
    "lead.created": {
      "post": {
        "summary": "Pressupost creat",
        "description": "S'ha creat un pressupost nou (des del programa, la web, l'email o l'assistent).\n\nLliurament POST signat amb X-PT-Signature (HMAC-SHA256 de \"<t>.<cos>\"). Respon 2xx en menys de 5 s.",
        "tags": [
          "Pressupostos"
        ],
        "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": "Rebut"
          }
        }
      }
    },
    "lead.sent": {
      "post": {
        "summary": "Pressupost enviat",
        "description": "El pressupost s'ha enviat al client.\n\nLliurament POST signat amb X-PT-Signature (HMAC-SHA256 de \"<t>.<cos>\"). Respon 2xx en menys de 5 s.",
        "tags": [
          "Pressupostos"
        ],
        "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": "Rebut"
          }
        }
      }
    },
    "lead.accepted": {
      "post": {
        "summary": "Pressupost acceptat",
        "description": "El client o el taller han aprovat el pressupost.\n\nLliurament POST signat amb X-PT-Signature (HMAC-SHA256 de \"<t>.<cos>\"). Respon 2xx en menys de 5 s.",
        "tags": [
          "Pressupostos"
        ],
        "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": "Rebut"
          }
        }
      }
    },
    "lead.rejected": {
      "post": {
        "summary": "Pressupost rebutjat",
        "description": "El pressupost s'ha rebutjat, amb el motiu si n'hi ha.\n\nLliurament POST signat amb X-PT-Signature (HMAC-SHA256 de \"<t>.<cos>\"). Respon 2xx en menys de 5 s.",
        "tags": [
          "Pressupostos"
        ],
        "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": "Rebut"
          }
        }
      }
    },
    "lead.status_changed": {
      "post": {
        "summary": "Canvi d'estat",
        "description": "Qualsevol canvi d'estat del pressupost (inclou els anteriors).\n\nLliurament POST signat amb X-PT-Signature (HMAC-SHA256 de \"<t>.<cos>\"). Respon 2xx en menys de 5 s.",
        "tags": [
          "Pressupostos"
        ],
        "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": "Rebut"
          }
        }
      }
    },
    "budget.accepted": {
      "post": {
        "summary": "Pressupost acceptat (budget)",
        "description": "Igual que lead.accepted, amb el nom nou: el pressupost ha passat a Aprobado (client, taller o integració amb budgets:accept).\n\nLliurament POST signat amb X-PT-Signature (HMAC-SHA256 de \"<t>.<cos>\"). Respon 2xx en menys de 5 s.",
        "tags": [
          "Pressupostos"
        ],
        "parameters": [
          {
            "name": "X-PT-Event",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string",
              "enum": [
                "budget.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": "Rebut"
          }
        }
      }
    },
    "budget.rejected": {
      "post": {
        "summary": "Pressupost rebutjat (budget)",
        "description": "Igual que lead.rejected, amb el nom nou: el pressupost ha passat a Rechazado, amb el seu motiu.\n\nLliurament POST signat amb X-PT-Signature (HMAC-SHA256 de \"<t>.<cos>\"). Respon 2xx en menys de 5 s.",
        "tags": [
          "Pressupostos"
        ],
        "parameters": [
          {
            "name": "X-PT-Event",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string",
              "enum": [
                "budget.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": "Rebut"
          }
        }
      }
    },
    "appointment.proposed": {
      "post": {
        "summary": "Cita proposada",
        "description": "Un client proposa una cita pendent que el taller la confirmi.\n\nLliurament POST signat amb X-PT-Signature (HMAC-SHA256 de \"<t>.<cos>\"). Respon 2xx en menys de 5 s.",
        "tags": [
          "Cites"
        ],
        "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": "Rebut"
          }
        }
      }
    },
    "appointment.confirmed": {
      "post": {
        "summary": "Cita confirmada",
        "description": "Una cita queda confirmada a l'agenda.\n\nLliurament POST signat amb X-PT-Signature (HMAC-SHA256 de \"<t>.<cos>\"). Respon 2xx en menys de 5 s.",
        "tags": [
          "Cites"
        ],
        "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": "Rebut"
          }
        }
      }
    },
    "appointment.cancelled": {
      "post": {
        "summary": "Cita cancel·lada",
        "description": "S'ha anul·lat la cita d'un pressupost.\n\nLliurament POST signat amb X-PT-Signature (HMAC-SHA256 de \"<t>.<cos>\"). Respon 2xx en menys de 5 s.",
        "tags": [
          "Cites"
        ],
        "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": "Rebut"
          }
        }
      }
    },
    "appointment.created": {
      "post": {
        "summary": "Cita creada",
        "description": "Un pressupost que no tenia cita queda citat a l'agenda (programa, web de seguiment, assistent o API).\n\nLliurament POST signat amb X-PT-Signature (HMAC-SHA256 de \"<t>.<cos>\"). Respon 2xx en menys de 5 s.",
        "tags": [
          "Cites"
        ],
        "parameters": [
          {
            "name": "X-PT-Event",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string",
              "enum": [
                "appointment.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": "Rebut"
          }
        }
      }
    },
    "appointment.updated": {
      "post": {
        "summary": "Cita canviada",
        "description": "S'ha mogut la data, l'hora o el box d'una cita existent, o s'ha canviat una proposta per API.\n\nLliurament POST signat amb X-PT-Signature (HMAC-SHA256 de \"<t>.<cos>\"). Respon 2xx en menys de 5 s.",
        "tags": [
          "Cites"
        ],
        "parameters": [
          {
            "name": "X-PT-Event",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string",
              "enum": [
                "appointment.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": "Rebut"
          }
        }
      }
    },
    "vehicle.created": {
      "post": {
        "summary": "Vehicle creat",
        "description": "S'ha donat d'alta un vehicle.\n\nLliurament POST signat amb X-PT-Signature (HMAC-SHA256 de \"<t>.<cos>\"). Respon 2xx en menys de 5 s.",
        "tags": [
          "Vehicles"
        ],
        "parameters": [
          {
            "name": "X-PT-Event",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string",
              "enum": [
                "vehicle.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": "Rebut"
          }
        }
      }
    },
    "vehicle.updated": {
      "post": {
        "summary": "Vehicle actualitzat",
        "description": "S'han modificat les dades o l'estat d'un vehicle.\n\nLliurament POST signat amb X-PT-Signature (HMAC-SHA256 de \"<t>.<cos>\"). Respon 2xx en menys de 5 s.",
        "tags": [
          "Vehicles"
        ],
        "parameters": [
          {
            "name": "X-PT-Event",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string",
              "enum": [
                "vehicle.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": "Rebut"
          }
        }
      }
    },
    "vehicle.checked_in": {
      "post": {
        "summary": "Vehicle rebut",
        "description": "El vehicle ha entrat al taller (recepció o sense cita).\n\nLliurament POST signat amb X-PT-Signature (HMAC-SHA256 de \"<t>.<cos>\"). Respon 2xx en menys de 5 s.",
        "tags": [
          "Vehicles"
        ],
        "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": "Rebut"
          }
        }
      }
    },
    "vehicle.arriving": {
      "post": {
        "summary": "Client de camí",
        "description": "El client avisa des del seu enllaç d'entrada que va de camí, arribarà tard o ja és al taller, amb l'hora estimada.\n\nLliurament POST signat amb X-PT-Signature (HMAC-SHA256 de \"<t>.<cos>\"). Respon 2xx en menys de 5 s.",
        "tags": [
          "Vehicles"
        ],
        "parameters": [
          {
            "name": "X-PT-Event",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string",
              "enum": [
                "vehicle.arriving"
              ]
            }
          },
          {
            "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": "Rebut"
          }
        }
      }
    },
    "vehicle.ready": {
      "post": {
        "summary": "Vehicle a punt",
        "description": "La reparació ha acabat i el vehicle es pot recollir.\n\nLliurament POST signat amb X-PT-Signature (HMAC-SHA256 de \"<t>.<cos>\"). Respon 2xx en menys de 5 s.",
        "tags": [
          "Vehicles"
        ],
        "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": "Rebut"
          }
        }
      }
    },
    "vehicle.arrived": {
      "post": {
        "summary": "Client a la recepció",
        "description": "El personal marca a la cua de recepció que el client ha arribat al taulell (l'entrada per quiosc o mòbil emet vehicle.checked_in).\n\nLliurament POST signat amb X-PT-Signature (HMAC-SHA256 de \"<t>.<cos>\"). Respon 2xx en menys de 5 s.",
        "tags": [
          "Vehicles"
        ],
        "parameters": [
          {
            "name": "X-PT-Event",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string",
              "enum": [
                "vehicle.arrived"
              ]
            }
          },
          {
            "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": "Rebut"
          }
        }
      }
    },
    "vehicle.eta_updated": {
      "post": {
        "summary": "Hora prevista de lliurament",
        "description": "S'ha fixat o canviat l'hora prevista de lliurament del vehicle; indica si s'ha comunicat al client.\n\nLliurament POST signat amb X-PT-Signature (HMAC-SHA256 de \"<t>.<cos>\"). Respon 2xx en menys de 5 s.",
        "tags": [
          "Vehicles"
        ],
        "parameters": [
          {
            "name": "X-PT-Event",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string",
              "enum": [
                "vehicle.eta_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": "Rebut"
          }
        }
      }
    },
    "vehicle.delivered": {
      "post": {
        "summary": "Vehicle lliurat",
        "description": "El client ha recollit el vehicle.\n\nLliurament POST signat amb X-PT-Signature (HMAC-SHA256 de \"<t>.<cos>\"). Respon 2xx en menys de 5 s.",
        "tags": [
          "Vehicles"
        ],
        "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": "Rebut"
          }
        }
      }
    },
    "vehicle.pickup_planned": {
      "post": {
        "summary": "Recollida planificada",
        "description": "El client ha triat el dia i la franja en què passarà a recollir el vehicle (date, slot: morning/afternoon/any).\n\nLliurament POST signat amb X-PT-Signature (HMAC-SHA256 de \"<t>.<cos>\"). Respon 2xx en menys de 5 s.",
        "tags": [
          "Vehicles"
        ],
        "parameters": [
          {
            "name": "X-PT-Event",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string",
              "enum": [
                "vehicle.pickup_planned"
              ]
            }
          },
          {
            "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": "Rebut"
          }
        }
      }
    },
    "courtesy_car.loaned": {
      "post": {
        "summary": "Cotxe de cortesia prestat",
        "description": "S'ha lliurat un cotxe de cortesia al client amb contracte signat (matrícula, km i combustible de sortida, devolució prevista).\n\nLliurament POST signat amb X-PT-Signature (HMAC-SHA256 de \"<t>.<cos>\"). Respon 2xx en menys de 5 s.",
        "tags": [
          "Vehicles"
        ],
        "parameters": [
          {
            "name": "X-PT-Event",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string",
              "enum": [
                "courtesy_car.loaned"
              ]
            }
          },
          {
            "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": "Rebut"
          }
        }
      }
    },
    "courtesy_car.returned": {
      "post": {
        "summary": "Cotxe de cortesia retornat",
        "description": "El client ha retornat el cotxe de cortesia (km i combustible d'entrada, dies i km extra calculats).\n\nLliurament POST signat amb X-PT-Signature (HMAC-SHA256 de \"<t>.<cos>\"). Respon 2xx en menys de 5 s.",
        "tags": [
          "Vehicles"
        ],
        "parameters": [
          {
            "name": "X-PT-Event",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string",
              "enum": [
                "courtesy_car.returned"
              ]
            }
          },
          {
            "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": "Rebut"
          }
        }
      }
    },
    "invoice.created": {
      "post": {
        "summary": "Factura creada",
        "description": "S'ha creat una factura (encara en esborrany, sense número definitiu), per exemple en facturar un pressupost.\n\nLliurament POST signat amb X-PT-Signature (HMAC-SHA256 de \"<t>.<cos>\"). Respon 2xx en menys de 5 s.",
        "tags": [
          "Factures"
        ],
        "parameters": [
          {
            "name": "X-PT-Event",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string",
              "enum": [
                "invoice.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": "Rebut"
          }
        }
      }
    },
    "invoice.issued": {
      "post": {
        "summary": "Factura emesa",
        "description": "S'ha emès una factura amb número definitiu.\n\nLliurament POST signat amb X-PT-Signature (HMAC-SHA256 de \"<t>.<cos>\"). Respon 2xx en menys de 5 s.",
        "tags": [
          "Factures"
        ],
        "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": "Rebut"
          }
        }
      }
    },
    "payment.received": {
      "post": {
        "summary": "Cobrament registrat",
        "description": "S'ha anotat un cobrament d'una factura.\n\nLliurament POST signat amb X-PT-Signature (HMAC-SHA256 de \"<t>.<cos>\"). Respon 2xx en menys de 5 s.",
        "tags": [
          "Factures"
        ],
        "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": "Rebut"
          }
        }
      }
    },
    "client.created": {
      "post": {
        "summary": "Client creat",
        "description": "S'ha donat d'alta un client.\n\nLliurament POST signat amb X-PT-Signature (HMAC-SHA256 de \"<t>.<cos>\"). Respon 2xx en menys de 5 s.",
        "tags": [
          "Clients"
        ],
        "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": "Rebut"
          }
        }
      }
    },
    "client.updated": {
      "post": {
        "summary": "Client actualitzat",
        "description": "S'han modificat les dades d'un client.\n\nLliurament POST signat amb X-PT-Signature (HMAC-SHA256 de \"<t>.<cos>\"). Respon 2xx en menys de 5 s.",
        "tags": [
          "Clients"
        ],
        "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": "Rebut"
          }
        }
      }
    },
    "communication.inbound": {
      "post": {
        "summary": "Missatge entrant",
        "description": "Ha arribat un email, SMS, WhatsApp o trucada d'un client.\n\nLliurament POST signat amb X-PT-Signature (HMAC-SHA256 de \"<t>.<cos>\"). Respon 2xx en menys de 5 s.",
        "tags": [
          "Comunicacions"
        ],
        "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": "Rebut"
          }
        }
      }
    },
    "message.received": {
      "post": {
        "summary": "Missatge rebut en una conversa",
        "description": "Un client ha escrit per WhatsApp, SMS, xat web o correu: conversa (clau estable canal:número), client, pressupost actiu, text, adjunts i data, i si l'assistent extern ha de contestar (external_bot).\n\nLliurament POST signat amb X-PT-Signature (HMAC-SHA256 de \"<t>.<cos>\"). Respon 2xx en menys de 5 s.",
        "tags": [
          "Comunicacions"
        ],
        "parameters": [
          {
            "name": "X-PT-Event",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string",
              "enum": [
                "message.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": "Rebut"
          }
        }
      }
    },
    "call.completed": {
      "post": {
        "summary": "Trucada analitzada",
        "description": "Una trucada (de l'equip o de l'assistent de veu) ha acabat i s'ha analitzat: resum, motiu, sentiment i transcripció si n'hi ha.\n\nLliurament POST signat amb X-PT-Signature (HMAC-SHA256 de \"<t>.<cos>\"). Respon 2xx en menys de 5 s.",
        "tags": [
          "Comunicacions"
        ],
        "parameters": [
          {
            "name": "X-PT-Event",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string",
              "enum": [
                "call.completed"
              ]
            }
          },
          {
            "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": "Rebut"
          }
        }
      }
    },
    "email.sent": {
      "post": {
        "summary": "Email enviat",
        "description": "S'ha enviat un email (campanyes, avisos o API).\n\nLliurament POST signat amb X-PT-Signature (HMAC-SHA256 de \"<t>.<cos>\"). Respon 2xx en menys de 5 s.",
        "tags": [
          "Comunicacions"
        ],
        "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": "Rebut"
          }
        }
      }
    },
    "email.failed": {
      "post": {
        "summary": "Email fallit",
        "description": "Un email no s'ha pogut enviar.\n\nLliurament POST signat amb X-PT-Signature (HMAC-SHA256 de \"<t>.<cos>\"). Respon 2xx en menys de 5 s.",
        "tags": [
          "Comunicacions"
        ],
        "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": "Rebut"
          }
        }
      }
    },
    "email.opened": {
      "post": {
        "summary": "Email obert",
        "description": "El destinatari ha obert l'email.\n\nLliurament POST signat amb X-PT-Signature (HMAC-SHA256 de \"<t>.<cos>\"). Respon 2xx en menys de 5 s.",
        "tags": [
          "Comunicacions"
        ],
        "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": "Rebut"
          }
        }
      }
    },
    "email.clicked": {
      "post": {
        "summary": "Clic a l'email",
        "description": "El destinatari ha clicat un enllaç de l'email.\n\nLliurament POST signat amb X-PT-Signature (HMAC-SHA256 de \"<t>.<cos>\"). Respon 2xx en menys de 5 s.",
        "tags": [
          "Comunicacions"
        ],
        "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": "Rebut"
          }
        }
      }
    },
    "email.unsubscribed": {
      "post": {
        "summary": "Baixa d'email",
        "description": "El destinatari s'ha donat de baixa dels emails.\n\nLliurament POST signat amb X-PT-Signature (HMAC-SHA256 de \"<t>.<cos>\"). Respon 2xx en menys de 5 s.",
        "tags": [
          "Comunicacions"
        ],
        "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": "Rebut"
          }
        }
      }
    },
    "email.bounced": {
      "post": {
        "summary": "Email rebotat",
        "description": "L'email ha rebotat.\n\nLliurament POST signat amb X-PT-Signature (HMAC-SHA256 de \"<t>.<cos>\"). Respon 2xx en menys de 5 s.",
        "tags": [
          "Comunicacions"
        ],
        "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": "Rebut"
          }
        }
      }
    },
    "sms.sent": {
      "post": {
        "summary": "SMS enviat",
        "description": "S'ha enviat un SMS.\n\nLliurament POST signat amb X-PT-Signature (HMAC-SHA256 de \"<t>.<cos>\"). Respon 2xx en menys de 5 s.",
        "tags": [
          "Comunicacions"
        ],
        "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": "Rebut"
          }
        }
      }
    },
    "sms.failed": {
      "post": {
        "summary": "SMS fallit",
        "description": "Un SMS no s'ha pogut enviar.\n\nLliurament POST signat amb X-PT-Signature (HMAC-SHA256 de \"<t>.<cos>\"). Respon 2xx en menys de 5 s.",
        "tags": [
          "Comunicacions"
        ],
        "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": "Rebut"
          }
        }
      }
    },
    "sms.unsubscribed": {
      "post": {
        "summary": "Baixa d'SMS",
        "description": "El destinatari ha demanat no rebre SMS.\n\nLliurament POST signat amb X-PT-Signature (HMAC-SHA256 de \"<t>.<cos>\"). Respon 2xx en menys de 5 s.",
        "tags": [
          "Comunicacions"
        ],
        "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": "Rebut"
          }
        }
      }
    },
    "whatsapp.unsubscribed": {
      "post": {
        "summary": "Baixa de WhatsApp",
        "description": "El destinatari ha demanat no rebre WhatsApp.\n\nLliurament POST signat amb X-PT-Signature (HMAC-SHA256 de \"<t>.<cos>\"). Respon 2xx en menys de 5 s.",
        "tags": [
          "Comunicacions"
        ],
        "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": "Rebut"
          }
        }
      }
    },
    "campaign.finished": {
      "post": {
        "summary": "Campanya acabada",
        "description": "Una campanya ha acabat d'enviar-se.\n\nLliurament POST signat amb X-PT-Signature (HMAC-SHA256 de \"<t>.<cos>\"). Respon 2xx en menys de 5 s.",
        "tags": [
          "Comunicacions"
        ],
        "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": "Rebut"
          }
        }
      }
    },
    "stock.low": {
      "post": {
        "summary": "Estoc baix",
        "description": "El disponible d'un recanvi ha baixat del seu estoc mínim (partId, name, unit, quantity, available, reserved, planned, minStock). S'envia un cop per canvi de nivell, no cada dia.\n\nLliurament POST signat amb X-PT-Signature (HMAC-SHA256 de \"<t>.<cos>\"). Respon 2xx en menys de 5 s.",
        "tags": [
          "Recanvis"
        ],
        "parameters": [
          {
            "name": "X-PT-Event",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string",
              "enum": [
                "stock.low"
              ]
            }
          },
          {
            "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": "Recibido"
          }
        }
      }
    },
    "stock.out": {
      "post": {
        "summary": "Recanvi esgotat",
        "description": "No queda res disponible d'un recanvi amb mínim o amb consum (mateixos camps que stock.low). Un cop per canvi de nivell.\n\nLliurament POST signat amb X-PT-Signature (HMAC-SHA256 de \"<t>.<cos>\"). Respon 2xx en menys de 5 s.",
        "tags": [
          "Recanvis"
        ],
        "parameters": [
          {
            "name": "X-PT-Event",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string",
              "enum": [
                "stock.out"
              ]
            }
          },
          {
            "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": "Recibido"
          }
        }
      }
    },
    "stock.shortage": {
      "post": {
        "summary": "Falta estoc en un pressupost acceptat",
        "description": "Un pressupost ha passat a Aprovat o En curs i no hi havia prou estoc: leadId i items [{ partId, missing }] amb el que queda pendent de demanar.\n\nLliurament POST signat amb X-PT-Signature (HMAC-SHA256 de \"<t>.<cos>\"). Respon 2xx en menys de 5 s.",
        "tags": [
          "Recanvis"
        ],
        "parameters": [
          {
            "name": "X-PT-Event",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string",
              "enum": [
                "stock.shortage"
              ]
            }
          },
          {
            "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": "Recibido"
          }
        }
      }
    }
  },
  "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": "Codi estable de l'error",
                "example": "insufficient_scope"
              },
              "message": {
                "type": "string",
                "description": "Missatge per a persones (pot canviar)",
                "example": "La clau API no té el permís «budgets:read»."
              }
            },
            "required": [
              "code",
              "message"
            ],
            "additionalProperties": false
          }
        },
        "description": "Format uniforme d'error",
        "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": "El cos de la petició no és vàlid: revisa la llista «fields»."
              },
              "fields": {
                "type": "array",
                "items": {
                  "type": "object",
                  "properties": {
                    "path": {
                      "type": "string",
                      "description": "Ruta del camp (lines[0].quantity)",
                      "example": "phone"
                    },
                    "message": {
                      "type": "string",
                      "example": "El format no és vàlid."
                    }
                  },
                  "required": [
                    "path",
                    "message"
                  ],
                  "additionalProperties": false
                }
              }
            },
            "required": [
              "code",
              "message",
              "fields"
            ],
            "additionalProperties": false
          }
        },
        "description": "422: cos que no compleix l'esquema",
        "required": [
          "error"
        ],
        "additionalProperties": false
      },
      "Page": {
        "type": "object",
        "properties": {
          "next_cursor": {
            "type": [
              "string",
              "null"
            ],
            "description": "Cursor opac per demanar la pàgina següent; null si no n'hi ha més",
            "example": "aWQ6MTIzNA"
          },
          "has_more": {
            "type": "boolean",
            "description": "true si queden més resultats",
            "example": true
          }
        },
        "description": "Camps de paginació que acompanyen `data` a tots els llistats",
        "required": [
          "next_cursor",
          "has_more"
        ],
        "additionalProperties": false
      },
      "StaffRef": {
        "type": "object",
        "properties": {
          "id": {
            "type": "integer",
            "description": "Id de l'usuari del taller",
            "example": 3
          },
          "name": {
            "type": [
              "string",
              "null"
            ],
            "description": "Nom visible",
            "example": "Marta"
          }
        },
        "description": "Persona del taller: només id i nom",
        "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": "Telèfon principal en format internacional quan se sap",
            "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 de les comunicacions (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": "No vol comunicacions comercials",
            "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": "Canals que el client ha demanat no fer servir",
            "required": [
              "email",
              "sms",
              "whatsapp",
              "call"
            ],
            "additionalProperties": false
          },
          "erased_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "Data de la sol·licitud d'esborrat RGPD; les dades personals ja estan anonimitzades",
            "example": null
          },
          "vehicles": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Vehicle"
            },
            "description": "Només amb ?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 de bastidor",
            "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": "Etiqueta DGT",
            "example": "C"
          },
          "km": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Últim quilometratge anotat en un pressupost",
            "example": 84500
          },
          "itv_expiry_date": {
            "type": [
              "string",
              "null"
            ],
            "format": "date",
            "description": "Venciment de la ITV",
            "example": "2027-03-15"
          },
          "status": {
            "type": "string",
            "description": "activo, baja_temporal o 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": "Canvi d'oli i filtre"
          },
          "extended_detail": {
            "type": [
              "string",
              "null"
            ],
            "description": "Detall ampliat visible per al client",
            "example": null
          },
          "reference": {
            "type": [
              "string",
              "null"
            ],
            "description": "Referència de recanvi",
            "example": null
          },
          "line_type": {
            "type": [
              "string",
              "null"
            ],
            "description": "labor, part, diagnosis, other…",
            "example": "labor"
          },
          "group_title": {
            "type": [
              "string",
              "null"
            ],
            "description": "Títol del grup de partides",
            "example": null
          },
          "quantity": {
            "type": "number",
            "example": 1
          },
          "unit_price": {
            "type": "number",
            "description": "Preu unitari de venda sense impostos",
            "example": 65
          },
          "discount_pct": {
            "type": "number",
            "description": "Descompte en percentatge",
            "example": 0
          },
          "tax_rate": {
            "type": "number",
            "description": "Tipus impositiu aplicat (IVA/IGIC)",
            "example": 21
          },
          "tax_exempt_code": {
            "type": [
              "string",
              "null"
            ],
            "example": null
          },
          "price_estimated": {
            "type": "boolean",
            "description": "Preu pendent de confirmar",
            "example": false
          },
          "base": {
            "type": "number",
            "description": "Base de la línia amb el descompte aplicat",
            "example": 65
          },
          "currency": {
            "type": "string",
            "enum": [
              "EUR"
            ],
            "example": "EUR"
          },
          "sort_order": {
            "type": "integer",
            "example": 0
          }
        },
        "description": "Partida de pressupost (mai no inclou el cost)",
        "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": "Import en euros amb dos decimals",
            "example": 65
          },
          "tax": {
            "type": "number",
            "description": "Import en euros amb dos decimals",
            "example": 13.65
          },
          "total": {
            "type": "number",
            "description": "Import en euros amb dos decimals",
            "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": "Import en euros amb dos decimals",
                  "example": 65
                },
                "quota": {
                  "type": "number",
                  "description": "Import en euros amb dos decimals",
                  "example": 13.65
                }
              },
              "required": [
                "rate",
                "base",
                "quota"
              ],
              "additionalProperties": false
            }
          }
        },
        "description": "Totals amb desglossament per tipus impositiu",
        "required": [
          "base",
          "tax",
          "total",
          "currency",
          "tax_rates"
        ],
        "additionalProperties": false
      },
      "Appointment": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "Identificador de la cita: b<pressupost> si està confirmada, p<proposta> si està pendent",
            "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": "Quan va confirmar el client (null si només la va fixar el taller)",
            "example": null
          },
          "budget_status": {
            "type": [
              "string",
              "null"
            ],
            "example": "Aprobado"
          },
          "checked_in_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "Entrada real del vehicle",
            "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 pel qual va arribar la proposta (web, portal, voice…)",
            "example": null
          },
          "proposed_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "Quan es va proposar (només status=proposed)",
            "example": null
          }
        },
        "description": "Cita confirmada o proposta pendent",
        "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 (valors en castellà)",
            "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": "Origen: web, email, telèfon, assistent, 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 del vehicle al taller",
            "example": null
          },
          "date_out": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "Sortida del vehicle",
            "example": null
          },
          "km": {
            "type": [
              "integer",
              "null"
            ],
            "example": 84500
          },
          "client_reference": {
            "type": [
              "string",
              "null"
            ],
            "description": "Referència que el client vol veure a la factura",
            "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": "Signatura del client en acceptar",
            "example": null
          },
          "estimated_ready_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "Hora prevista de lliurament del vehicle (recepció)",
            "example": null
          },
          "delivered_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "Lliurament del vehicle al client",
            "example": null
          },
          "reject_reason": {
            "type": [
              "string",
              "null"
            ],
            "example": null
          }
        },
        "description": "Pressupost (resum)",
        "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",
          "estimated_ready_at",
          "delivered_at",
          "reject_reason"
        ],
        "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
          },
          "estimated_ready_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
          },
          "public_notes": {
            "type": [
              "string",
              "null"
            ],
            "description": "Observacions visibles per al client",
            "example": "Revisar també el soroll a la suspensió."
          },
          "totals": {
            "$ref": "#/components/schemas/Totals"
          },
          "lines": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/BudgetLine"
            }
          }
        },
        "description": "Pressupost amb partides i totals",
        "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",
          "estimated_ready_at",
          "delivered_at",
          "reject_reason",
          "public_notes",
          "totals",
          "lines"
        ],
        "additionalProperties": false
      },
      "InvoiceLine": {
        "type": "object",
        "properties": {
          "id": {
            "type": "integer",
            "example": 9001
          },
          "description": {
            "type": [
              "string",
              "null"
            ],
            "example": "Canvi d'oli i filtre"
          },
          "reference": {
            "type": [
              "string",
              "null"
            ],
            "example": null
          },
          "group_title": {
            "type": [
              "string",
              "null"
            ],
            "example": null
          },
          "quantity": {
            "type": "number",
            "example": 1
          },
          "unit_price": {
            "type": "number",
            "description": "Import en euros amb dos decimals",
            "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": "Import en euros amb dos decimals",
            "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": "Import en euros amb dos decimals",
            "example": 78.65
          },
          "pending": {
            "type": "number",
            "description": "Import en euros amb dos decimals",
            "example": 0
          },
          "settled": {
            "type": "boolean",
            "example": true
          }
        },
        "description": "Resum de cobraments",
        "required": [
          "paid",
          "pending",
          "settled"
        ],
        "additionalProperties": false
      },
      "Invoice": {
        "type": "object",
        "properties": {
          "id": {
            "type": "integer",
            "example": 412
          },
          "number": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Número dins de la 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 de la factura rectificada",
            "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": "Import en euros amb dos decimals",
            "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": "Règim especial de béns usats",
            "example": false
          },
          "verifactu_hash": {
            "type": [
              "string",
              "null"
            ],
            "description": "Empremta Verifactu de la factura emesa",
            "example": "3f9a…"
          },
          "registered_externally": {
            "type": "boolean",
            "description": "Emesa fora del programa i registrada amb el seu número original; no té empremta Verifactu del programa",
            "example": false
          }
        },
        "description": "Factura (resum)",
        "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",
          "registered_externally"
        ],
        "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": "Import en euros amb dos decimals",
            "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…"
          },
          "registered_externally": {
            "type": "boolean",
            "example": false
          },
          "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": "Dades fiscals tal com van quedar a la factura",
            "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": "Import en euros amb dos decimals",
                  "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": "Factura amb línies i cobraments",
        "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",
          "registered_externally",
          "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": "Preu orientatiu de venda, si el taller l'ha fixat",
            "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": "Canvi d'oli"
                },
                "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 servei amb les subcategories",
        "required": [
          "id",
          "name",
          "reference_price",
          "currency",
          "subcategories"
        ],
        "additionalProperties": false
      },
      "LaborRate": {
        "type": "object",
        "properties": {
          "id": {
            "type": "integer",
            "example": 1
          },
          "name": {
            "type": "string",
            "example": "Mà d'obra general"
          },
          "price_per_hour": {
            "type": [
              "number",
              "null"
            ],
            "description": "Preu de venda per hora sense impostos",
            "example": 48
          },
          "currency": {
            "type": "string",
            "enum": [
              "EUR"
            ],
            "example": "EUR"
          },
          "is_default": {
            "type": "boolean",
            "example": true
          }
        },
        "description": "Tarifa de mà d'obra (només preu de venda)",
        "required": [
          "id",
          "name",
          "price_per_hour",
          "currency",
          "is_default"
        ],
        "additionalProperties": false
      },
      "StockItem": {
        "type": "object",
        "properties": {
          "id": {
            "type": "integer",
            "example": 88
          },
          "name": {
            "type": "string",
            "example": "Turbocompresor"
          },
          "brand": {
            "type": [
              "string",
              "null"
            ],
            "example": "Garrett"
          },
          "references": {
            "type": "object",
            "properties": {
              "original": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "Referència del fabricant del vehicle (OEM)",
                "example": "A6540907801"
              },
              "supplier": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "Referència del distribuïdor",
                "example": "MA654 090 78 01"
              },
              "own": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "Referència interna del taller",
                "example": null
              }
            },
            "description": "Referències de la peça",
            "required": [
              "original",
              "supplier",
              "own"
            ],
            "additionalProperties": false
          },
          "ean": {
            "type": [
              "string",
              "null"
            ],
            "description": "Codi de barres EAN-13",
            "example": null
          },
          "unit": {
            "type": "string",
            "enum": [
              "ud",
              "par",
              "juego",
              "caja",
              "rollo",
              "L",
              "kg",
              "g",
              "m"
            ],
            "description": "Unitat de mesura. ud, par, juego, caja i rollo només admeten quantitats senceres; L, kg, g i m admeten fins a 3 decimals",
            "example": "ud"
          },
          "quantity": {
            "type": "number",
            "description": "Quantitat física al magatzem (fins a 3 decimals)",
            "example": 4.5
          },
          "reserved": {
            "type": "number",
            "description": "Bloquejat per pressupostos acceptats o en curs",
            "example": 1
          },
          "pending": {
            "type": "number",
            "description": "El que falta en aquests pressupostos acceptats perquè no hi havia estoc (pendent de demanar)",
            "example": 0
          },
          "planned": {
            "type": "number",
            "description": "Previst en pressupostos oberts (Pendent, En cotització, Enviat): no bloqueja",
            "example": 2
          },
          "available": {
            "type": "number",
            "description": "Disponible = quantity − reserved",
            "example": 3.5
          },
          "min_stock": {
            "type": [
              "number",
              "null"
            ],
            "description": "Estoc mínim configurat (null si no n'hi ha)",
            "example": 2
          },
          "location": {
            "type": [
              "string",
              "null"
            ],
            "description": "Ubicació al magatzem",
            "example": "Prestatgeria B3"
          },
          "status": {
            "type": "string",
            "description": "nou, usat o reacondicionat",
            "example": "nuevo"
          },
          "sale_price": {
            "type": [
              "number",
              "null"
            ],
            "description": "Preu de venda per unitat sense impostos",
            "example": 2190
          },
          "currency": {
            "type": "string",
            "enum": [
              "EUR"
            ],
            "example": "EUR"
          },
          "active": {
            "type": "boolean",
            "description": "false si la peça s'ha donat de baixa de l'inventari",
            "example": true
          },
          "updated_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "example": "2026-10-06T09:30:00.000Z"
          }
        },
        "description": "Recanvi de l'estoc (sense cost de compra ni proveïdor)",
        "required": [
          "id",
          "name",
          "brand",
          "references",
          "ean",
          "unit",
          "quantity",
          "reserved",
          "pending",
          "planned",
          "available",
          "min_stock",
          "location",
          "status",
          "sale_price",
          "currency",
          "active",
          "updated_at"
        ],
        "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: el client proposa i el taller confirma; book: el client reserva directament",
            "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": "Horari setmanal de l'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": "Festius dels propers 90 dies (nacionals, autonòmics i els del taller)"
          }
        },
        "description": "Dades públiques del taller",
        "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": "El seu pressupost"
          },
          "body": {
            "type": [
              "string",
              "null"
            ],
            "description": "Contingut complet",
            "example": "Hola 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": "Segons (només trucades)",
            "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": "Comunicació 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
              },
              "client": {
                "oneOf": [
                  {
                    "$ref": "#/components/schemas/ClientRef"
                  },
                  {
                    "type": "null"
                  }
                ],
                "description": "Clau de client: l'únic client les dades del qual pot veure i tocar. null = clau del taller (tots els clients)."
              }
            },
            "required": [
              "id",
              "name",
              "environment",
              "scopes",
              "expires_at",
              "client"
            ],
            "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 de la instància (host)",
            "example": "taller.ejemplo.com"
          },
          "server_time": {
            "type": "string",
            "example": "2026-10-06T09:30:00.000Z"
          },
          "version": {
            "type": "string",
            "description": "Versió de l'API",
            "example": "v1"
          }
        },
        "description": "Estat de la clau",
        "required": [
          "ok",
          "key",
          "rate_limit",
          "instance",
          "server_time",
          "version"
        ],
        "additionalProperties": false
      },
      "Slot": {
        "type": "object",
        "properties": {
          "start": {
            "type": "string",
            "description": "Inici (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": "Forat lliure",
        "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": "Forats lliures amb la mateixa lògica que l'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: cita confirmada anul·lada; 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": "Visible per al client al seu enllaç de seguiment",
            "example": false
          },
          "mechanic_visible": {
            "type": "boolean",
            "description": "Visible a l'app del mecànic",
            "example": false
          },
          "created_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "example": "2026-10-06T09:30:00.000Z"
          }
        },
        "description": "Document adjunt a un pressupost",
        "required": [
          "id",
          "budget_id",
          "filename",
          "content_type",
          "size",
          "url",
          "client_visible",
          "mechanic_visible",
          "created_at"
        ],
        "additionalProperties": false
      },
      "VehicleHistoryVisit": {
        "type": "object",
        "properties": {
          "budget_id": {
            "type": "integer",
            "example": 1234
          },
          "status": {
            "type": [
              "string",
              "null"
            ],
            "example": "Facturado"
          },
          "created_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "example": "2026-10-06T09:30:00.000Z"
          },
          "checked_in_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "Entrada al taller",
            "example": "2026-09-02T07:30:00.000Z"
          },
          "delivered_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "Sortida del taller",
            "example": "2026-09-03T16:00:00.000Z"
          },
          "km": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Quilometratge anotat a la visita",
            "example": 84210
          },
          "amount": {
            "type": [
              "number",
              "null"
            ],
            "description": "Import del pressupost (euros, IVA inclòs)",
            "example": 245.3
          },
          "invoice": {
            "type": [
              "string",
              "null"
            ],
            "description": "Número de factura si n'hi ha",
            "example": "F26/118"
          },
          "category": {
            "type": [
              "string",
              "null"
            ],
            "example": "Mantenimiento"
          },
          "works": {
            "type": "array",
            "items": {
              "type": "string",
              "example": "Canvi d'oli i filtre"
            },
            "description": "Conceptes de les partides (sense preus de cost)"
          }
        },
        "description": "Visita del vehicle al taller",
        "required": [
          "budget_id",
          "status",
          "created_at",
          "checked_in_at",
          "delivered_at",
          "km",
          "amount",
          "invoice",
          "category",
          "works"
        ],
        "additionalProperties": false
      },
      "VehicleHistory": {
        "type": "object",
        "properties": {
          "vehicle": {
            "$ref": "#/components/schemas/Vehicle"
          },
          "visits": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/VehicleHistoryVisit"
            },
            "description": "Pressupostos del vehicle, del més recent al més antic (sense els d'ús intern)"
          },
          "external_interventions": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "title": {
                  "type": [
                    "string",
                    "null"
                  ],
                  "example": "Canvi de pneumàtics"
                },
                "date": {
                  "type": [
                    "string",
                    "null"
                  ],
                  "format": "date",
                  "example": "2025-11-20"
                },
                "km": {
                  "type": [
                    "integer",
                    "null"
                  ],
                  "example": 78000
                },
                "workshop_name": {
                  "type": [
                    "string",
                    "null"
                  ],
                  "description": "Un altre taller (si l'ha anotat el client o el taller)",
                  "example": null
                },
                "warranty_until": {
                  "type": [
                    "string",
                    "null"
                  ],
                  "format": "date",
                  "example": null
                }
              },
              "required": [
                "title",
                "date",
                "km",
                "workshop_name",
                "warranty_until"
              ],
              "additionalProperties": false
            },
            "description": "Intervencions anotades fora d'aquest taller"
          }
        },
        "description": "Historial de reparacions i visites d'un vehicle",
        "required": [
          "vehicle",
          "visits",
          "external_interventions"
        ],
        "additionalProperties": false
      },
      "ActivityEntry": {
        "type": "object",
        "properties": {
          "id": {
            "type": "integer",
            "example": 88123
          },
          "at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "example": "2026-10-06T09:30:00.000Z"
          },
          "event": {
            "type": [
              "string",
              "null"
            ],
            "description": "Tipus d'esdeveniment (status_changed, web_accepted, invoiced, client_chat_message…)",
            "example": "web_accepted"
          },
          "detail": {
            "type": [
              "string",
              "null"
            ],
            "example": "Pressupost acceptat pel client des de la web de seguiment"
          },
          "source": {
            "type": [
              "string",
              "null"
            ],
            "description": "staff, client, system…",
            "example": "client"
          },
          "public": {
            "type": "boolean",
            "description": "true si el client ho veu al seu enllaç de seguiment",
            "example": true
          },
          "user": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/StaffRef"
              },
              {
                "type": "null"
              }
            ]
          }
        },
        "description": "Entrada de l'historial del pressupost",
        "required": [
          "id",
          "at",
          "event",
          "detail",
          "source",
          "public",
          "user"
        ],
        "additionalProperties": false
      },
      "BudgetDecision": {
        "type": "object",
        "properties": {
          "budget": {
            "$ref": "#/components/schemas/BudgetDetail"
          },
          "already": {
            "type": "boolean",
            "description": "true si ja era en aquest estat (no s'ha canviat res)",
            "example": false
          },
          "signature": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "waived_by_integration",
              "not_required",
              null
            ],
            "description": "Només en acceptar: waived_by_integration si el taller exigeix signatura i l'API ha acceptat sense ella",
            "example": "not_required"
          }
        },
        "description": "Resultat d'acceptar o rebutjar en nom del client",
        "required": [
          "budget",
          "already",
          "signature"
        ],
        "additionalProperties": false
      },
      "InvoiceCreated": {
        "type": "object",
        "properties": {
          "invoice": {
            "$ref": "#/components/schemas/InvoiceDetail"
          },
          "created": {
            "type": "boolean",
            "description": "false si el pressupost ja tenia factura i es retorna aquesta",
            "example": true
          }
        },
        "description": "Factura emesa des d'un pressupost",
        "required": [
          "invoice",
          "created"
        ],
        "additionalProperties": false
      },
      "Conversation": {
        "type": "object",
        "properties": {
          "key": {
            "type": "string",
            "description": "Clau estable: whatsapp:<9 dígits>, sms:<9 dígits>, chat:<id> o email:<id>",
            "example": "whatsapp:600111222"
          },
          "channel": {
            "type": "string",
            "enum": [
              "whatsapp",
              "sms",
              "chat",
              "email"
            ],
            "example": "whatsapp"
          },
          "contact": {
            "type": [
              "string",
              "null"
            ],
            "description": "Número, correu o identificador del visitant",
            "example": "+34600111222"
          },
          "client": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/ClientRef"
              },
              {
                "type": "null"
              }
            ]
          },
          "budget_id": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Pressupost actiu de la conversa",
            "example": 1234
          },
          "status": {
            "type": "string",
            "enum": [
              "open",
              "pending",
              "closed"
            ],
            "description": "open: falta contestar; pending: esperant el client; closed: tancada",
            "example": "open"
          },
          "unread": {
            "type": "integer",
            "example": 1
          },
          "last_message_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "example": "2026-10-06T09:30:00.000Z"
          },
          "last_inbound_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "example": "2026-10-06T09:29:00.000Z"
          },
          "last_message": {
            "type": [
              "string",
              "null"
            ],
            "example": "A quina hora puc passar a recollir el cotxe?"
          },
          "last_author": {
            "type": "string",
            "enum": [
              "cliente",
              "ia",
              "equipo",
              "automatico"
            ],
            "example": "cliente"
          },
          "whatsapp_window_open": {
            "type": "boolean",
            "description": "Només WhatsApp: true si es pot escriure text lliure (finestra de 24 h)",
            "example": true
          },
          "external_bot": {
            "type": "boolean",
            "description": "true si el canal és en mode assistent extern",
            "example": false
          }
        },
        "description": "Conversa de la Safata d'entrada",
        "required": [
          "key",
          "channel",
          "contact",
          "client",
          "budget_id",
          "status",
          "unread",
          "last_message_at",
          "last_inbound_at",
          "last_message",
          "last_author",
          "whatsapp_window_open",
          "external_bot"
        ],
        "additionalProperties": false
      },
      "ConversationMessage": {
        "type": "object",
        "properties": {
          "id": {
            "type": "integer",
            "example": 77120
          },
          "at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "example": "2026-10-06T09:30:00.000Z"
          },
          "direction": {
            "type": "string",
            "enum": [
              "in",
              "out"
            ],
            "example": "in"
          },
          "author": {
            "type": "string",
            "enum": [
              "cliente",
              "ia",
              "equipo",
              "automatico",
              "integracion"
            ],
            "example": "cliente"
          },
          "author_name": {
            "type": [
              "string",
              "null"
            ],
            "description": "Persona de l'equip o integració que ha escrit",
            "example": null
          },
          "subject": {
            "type": [
              "string",
              "null"
            ],
            "description": "Només correu",
            "example": null
          },
          "text": {
            "type": [
              "string",
              "null"
            ],
            "example": "A quina hora puc passar a recollir el cotxe?"
          },
          "attachments": {
            "type": "array",
            "items": {
              "type": "string",
              "example": "https://…/whatsapp-in/SM123-0.jpg"
            },
            "description": "URLs dels adjunts"
          },
          "status": {
            "type": [
              "string",
              "null"
            ],
            "example": "received"
          }
        },
        "description": "Missatge d'una conversa",
        "required": [
          "id",
          "at",
          "direction",
          "author",
          "author_name",
          "subject",
          "text",
          "attachments",
          "status"
        ],
        "additionalProperties": false
      },
      "MessageSent": {
        "type": "object",
        "properties": {
          "ok": {
            "type": "boolean",
            "example": true
          },
          "key": {
            "type": "string",
            "example": "whatsapp:600111222"
          },
          "channel": {
            "type": "string",
            "enum": [
              "whatsapp",
              "sms",
              "chat",
              "email"
            ],
            "example": "whatsapp"
          },
          "mode": {
            "type": "string",
            "enum": [
              "free",
              "template",
              "text",
              "email"
            ],
            "example": "free"
          },
          "simulated": {
            "type": "boolean",
            "description": "true a l'entorn de proves: no ha sortit de debò",
            "example": false
          }
        },
        "description": "Missatge enviat",
        "required": [
          "ok",
          "key",
          "channel",
          "mode",
          "simulated"
        ],
        "additionalProperties": false
      },
      "WhatsAppTemplate": {
        "type": "object",
        "properties": {
          "id": {
            "type": "integer",
            "example": 12
          },
          "name": {
            "type": "string",
            "example": "presupuesto_listo"
          },
          "language": {
            "type": "string",
            "example": "es"
          },
          "category": {
            "type": "string",
            "example": "UTILITY"
          },
          "body": {
            "type": "string",
            "description": "Text amb les seves etiquetes (#nombre#, #total#…)",
            "example": "Hola #nombre#, el seu pressupost està llest."
          },
          "variables": {
            "type": "array",
            "items": {
              "type": "string",
              "example": "#nombre#"
            },
            "description": "Etiquetes que admet template_variables"
          }
        },
        "description": "Plantilla de WhatsApp aprovada per Meta",
        "required": [
          "id",
          "name",
          "language",
          "category",
          "body",
          "variables"
        ],
        "additionalProperties": false
      },
      "AssistantSettings": {
        "type": "object",
        "properties": {
          "external_channels": {
            "type": "array",
            "items": {
              "type": "string",
              "enum": [
                "whatsapp",
                "sms",
                "chat",
                "email"
              ],
              "example": "whatsapp"
            },
            "description": "Canals en què contesta l'assistent extern (el del programa deixa de fer-ho)"
          },
          "api_key_id": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Clau de la integració que contesta; si es revoca o caduca, torna a contestar l'assistent del programa",
            "example": 7
          },
          "fallback_minutes": {
            "type": "integer",
            "description": "Minuts sense resposta de la integració passats els quals contesta l'assistent del programa",
            "example": 10
          },
          "channels_owned_now": {
            "type": "array",
            "items": {
              "type": "string",
              "enum": [
                "whatsapp",
                "sms",
                "chat",
                "email"
              ]
            },
            "description": "Canals que la integració atén ara mateix (clau viva i webhook message.received actiu)"
          }
        },
        "description": "Mode assistent extern per canal",
        "required": [
          "external_channels",
          "api_key_id",
          "fallback_minutes",
          "channels_owned_now"
        ],
        "additionalProperties": false
      },
      "SandboxKey": {
        "type": "object",
        "properties": {
          "key": {
            "type": "string",
            "description": "Clau de proves: només es mostra una vegada",
            "example": "pt_test_0123456789abcdef0123456789abcdef"
          },
          "expires_at": {
            "type": "string",
            "example": "2026-11-12T10:00:00.000Z",
            "format": "date-time"
          },
          "base_url": {
            "type": "string",
            "example": "https://demo.programa-taller.es/api/v1"
          },
          "docs_url": {
            "type": "string",
            "example": "https://demo.programa-taller.es/api/v1/openapi.json"
          },
          "scopes": {
            "type": "array",
            "items": {
              "type": "string",
              "example": "budgets:accept"
            }
          }
        },
        "description": "Clau de l'entorn de proves",
        "required": [
          "key",
          "expires_at",
          "base_url",
          "docs_url",
          "scopes"
        ],
        "additionalProperties": false
      },
      "WebhookEnvelope": {
        "type": "object",
        "properties": {
          "event": {
            "type": "string",
            "description": "Nom de l'esdeveniment",
            "example": "lead.accepted"
          },
          "timestamp": {
            "type": "string",
            "example": "2026-10-06T10:15:00.000Z"
          },
          "data": {
            "type": "object",
            "description": "Càrrega específica de l'esdeveniment",
            "additionalProperties": true,
            "example": {
              "leadId": 1234,
              "from": "Enviado",
              "to": "Aprobado"
            }
          }
        },
        "description": "Cos de tot lliurament 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": "Subscripció a esdeveniments (mai no inclou el secret)",
        "required": [
          "id",
          "url",
          "events",
          "description",
          "active",
          "created_at"
        ],
        "additionalProperties": false
      }
    },
    "x-scopes": [
      {
        "scope": "clients:read",
        "area": "clients",
        "label": "Llegir clients",
        "description": "Consultar fitxes de clients i les seves dades de contacte.",
        "sideEffect": false,
        "adminOnly": false
      },
      {
        "scope": "clients:write",
        "area": "clients",
        "label": "Crear i editar clients",
        "description": "Donar d'alta clients nous i modificar els existents.",
        "sideEffect": true,
        "adminOnly": false
      },
      {
        "scope": "vehicles:read",
        "area": "vehicles",
        "label": "Llegir vehicles",
        "description": "Consultar vehicles, matrícules i el seu historial.",
        "sideEffect": false,
        "adminOnly": false
      },
      {
        "scope": "vehicles:write",
        "area": "vehicles",
        "label": "Crear i editar vehicles",
        "description": "Donar d'alta vehicles i modificar-ne les dades.",
        "sideEffect": true,
        "adminOnly": false
      },
      {
        "scope": "budgets:read",
        "area": "budgets",
        "label": "Llegir pressupostos",
        "description": "Consultar pressupostos, les seves partides i el seu estat.",
        "sideEffect": false,
        "adminOnly": false
      },
      {
        "scope": "budgets:write",
        "area": "budgets",
        "label": "Crear i editar pressupostos",
        "description": "Crear pressupostos, afegir partides i canviar-ne l'estat.",
        "sideEffect": true,
        "adminOnly": false
      },
      {
        "scope": "budgets:accept",
        "area": "budgets",
        "label": "Acceptar o rebutjar en nom del client",
        "description": "Acceptar un pressupost en nom del client amb la seva evidència (qui i per quin canal) o rebutjar-lo amb motiu. Queda a l'historial amb el nom de la clau.",
        "sideEffect": true,
        "adminOnly": true,
        "warning": "Permet acceptar pressupostos en nom del client: la integració respon que el client l'ha acceptat de debò."
      },
      {
        "scope": "invoices:read",
        "area": "invoices",
        "label": "Llegir factures",
        "description": "Consultar factures emeses, imports i cobraments.",
        "sideEffect": false,
        "adminOnly": false
      },
      {
        "scope": "invoices:write",
        "area": "invoices",
        "label": "Emetre factures i anotar cobraments",
        "description": "Emetre la factura d'un pressupost (numeració, sèrie, Verifactu i comptabilitat del programa) i registrar cobraments.",
        "sideEffect": true,
        "adminOnly": true,
        "warning": "Permet emetre factures legals amb numeració i Verifactu: una factura emesa no s'esborra, només es rectifica des del programa."
      },
      {
        "scope": "appointments:read",
        "area": "appointments",
        "label": "Llegir cites",
        "description": "Consultar l'agenda de cites i els forats disponibles.",
        "sideEffect": false,
        "adminOnly": false
      },
      {
        "scope": "appointments:write",
        "area": "appointments",
        "label": "Reservar i cancel·lar cites",
        "description": "Crear, moure i cancel·lar cites a l'agenda.",
        "sideEffect": true,
        "adminOnly": false
      },
      {
        "scope": "communications:read",
        "area": "communications",
        "label": "Llegir comunicacions",
        "description": "Consultar el registre d'emails, SMS, WhatsApp i trucades (inclou el contingut complet).",
        "sideEffect": false,
        "adminOnly": false
      },
      {
        "scope": "communications:send",
        "area": "communications",
        "label": "Enviar email i SMS",
        "description": "Enviar emails i SMS transaccionals des de la instància; consumeix saldo.",
        "sideEffect": true,
        "adminOnly": false
      },
      {
        "scope": "messages:send",
        "area": "communications",
        "label": "Respondre converses",
        "description": "Respondre com el taller a les converses de WhatsApp, SMS, xat web i correu (bot o IA externa); consumeix saldo.",
        "sideEffect": true,
        "adminOnly": true,
        "warning": "Permet escriure als clients en nom del taller per WhatsApp, SMS, xat i correu."
      },
      {
        "scope": "catalog:read",
        "area": "catalog",
        "label": "Llegir catàleg",
        "description": "Consultar serveis, tarifes i conceptes del tarifari.",
        "sideEffect": false,
        "adminOnly": false
      },
      {
        "scope": "stock:read",
        "area": "stock",
        "label": "Llegir estoc",
        "description": "Consultar existències i referències de recanvis.",
        "sideEffect": false,
        "adminOnly": false
      },
      {
        "scope": "stock:write",
        "area": "stock",
        "label": "Ajustar estoc",
        "description": "Donar entrades i sortides de recanvis.",
        "sideEffect": true,
        "adminOnly": false
      },
      {
        "scope": "webhooks:manage",
        "area": "webhooks",
        "label": "Gestionar webhooks",
        "description": "Crear, llistar i esborrar les subscripcions a esdeveniments d'aquesta clau.",
        "sideEffect": true,
        "adminOnly": false
      },
      {
        "scope": "settings:write",
        "area": "settings",
        "label": "Ajustos de l'assistent extern",
        "description": "Consultar i canviar en quins canals contesta un assistent extern en lloc de l'assistent del programa.",
        "sideEffect": true,
        "adminOnly": true,
        "warning": "Permet apagar l'assistent del programa als canals que triï la integració."
      },
      {
        "scope": "reports:read",
        "area": "reports",
        "label": "Llegir informes",
        "description": "Consultar xifres agregades de facturació, activitat i rendiment.",
        "sideEffect": false,
        "adminOnly": false
      }
    ],
    "x-error-codes": [
      {
        "code": "missing_api_key",
        "status": 401,
        "message": "Falta la clau API: envia-la a la capçalera Authorization: Bearer pt_… o X-Api-Key."
      },
      {
        "code": "invalid_api_key",
        "status": 401,
        "message": "La clau API no és vàlida per a aquesta instància."
      },
      {
        "code": "revoked_api_key",
        "status": 401,
        "message": "La clau API està revocada."
      },
      {
        "code": "expired_api_key",
        "status": 401,
        "message": "La clau API ha caducat."
      },
      {
        "code": "ip_not_allowed",
        "status": 403,
        "message": "L'adreça IP d'origen no és a la llista permesa d'aquesta clau."
      },
      {
        "code": "insufficient_scope",
        "status": 403,
        "message": "La clau API no té el permís necessari per a aquesta operació."
      },
      {
        "code": "test_key_forbidden",
        "status": 403,
        "message": "Una clau de prova (pt_test_) no pot fer operacions amb efectes: fes servir una clau live."
      },
      {
        "code": "rate_limited",
        "status": 429,
        "message": "Has superat el límit de peticions d'aquesta clau. Espera i torna-ho a provar."
      },
      {
        "code": "instance_rate_limited",
        "status": 503,
        "message": "La instància està rebent massa peticions per API en aquest moment. Torna-ho a provar d'aquí a uns segons."
      },
      {
        "code": "plan_quota_exceeded",
        "status": 429,
        "message": "S'ha esgotat la quota diària de peticions del pla d'API del taller. Es renova a les 00:00 UTC; per a més volum, millora el pla."
      },
      {
        "code": "plan_scope_not_allowed",
        "status": 403,
        "message": "El pla d'API del taller no inclou aquest permís. Per fer-lo servir cal millorar el pla."
      },
      {
        "code": "bad_request",
        "status": 400,
        "message": "La petició no és vàlida."
      },
      {
        "code": "not_found",
        "status": 404,
        "message": "No s'ha trobat el recurs."
      },
      {
        "code": "upstream_error",
        "status": 502,
        "message": "Un servei extern ha rebutjat l'operació."
      },
      {
        "code": "internal_error",
        "status": 500,
        "message": "Error intern."
      },
      {
        "code": "validation_error",
        "status": 422,
        "message": "El cos de la petició no és vàlid: revisa la llista «fields»."
      },
      {
        "code": "idempotency_key_required",
        "status": 400,
        "message": "Falta la capçalera Idempotency-Key (obligatòria a tot POST; de 8 a 255 caràcters visibles)."
      },
      {
        "code": "idempotency_conflict",
        "status": 409,
        "message": "Aquesta Idempotency-Key ja s'ha fet servir les últimes 24 h amb una altra petició."
      },
      {
        "code": "idempotency_in_progress",
        "status": 409,
        "message": "Hi ha una altra petició amb la mateixa Idempotency-Key en curs. Torna-ho a provar d'aquí a uns segons."
      },
      {
        "code": "client_exists",
        "status": 409,
        "message": "Ja existeix un client amb aquest telèfon, email o NIF/CIF."
      },
      {
        "code": "client_erased",
        "status": 409,
        "message": "El client va demanar l'esborrat de les seves dades (RGPD): la fitxa no admet canvis ni altes associades."
      },
      {
        "code": "vehicle_exists",
        "status": 409,
        "message": "Aquest client ja té un vehicle amb aquesta matrícula."
      },
      {
        "code": "vehicle_belongs_to_other_client",
        "status": 409,
        "message": "Aquesta matrícula ja està donada d'alta a nom d'un altre client."
      },
      {
        "code": "budget_locked",
        "status": 409,
        "message": "El pressupost està tancat i ja no admet aquest canvi."
      },
      {
        "code": "invalid_status_transition",
        "status": 409,
        "message": "El pressupost no pot passar a aquest estat des de l'estat actual."
      },
      {
        "code": "status_transition_forbidden",
        "status": 403,
        "message": "Aquest canvi d'estat no està disponible per API."
      },
      {
        "code": "client_acceptance_required",
        "status": 403,
        "message": "Per acceptar un pressupost fes servir POST /budgets/{id}/accept amb el permís budgets:accept i l'evidència de qui accepta."
      },
      {
        "code": "booking_mode_propose_only",
        "status": 403,
        "message": "El taller treballa en mode «proposar cita»: només es poden crear propostes que el taller confirma."
      },
      {
        "code": "slot_unavailable",
        "status": 409,
        "message": "Aquest forat no està disponible."
      },
      {
        "code": "payload_too_large",
        "status": 413,
        "message": "El fitxer supera la mida màxima (4 MB)."
      },
      {
        "code": "unsupported_media_type",
        "status": 415,
        "message": "Tipus de fitxer no admès: només JPEG, PNG, WebP o PDF."
      },
      {
        "code": "fiscal_data_incomplete",
        "status": 422,
        "message": "Falten dades fiscals del client per poder facturar: revisa la llista «missing»."
      },
      {
        "code": "budget_not_invoiceable",
        "status": 409,
        "message": "El pressupost no es pot facturar en el seu estat actual."
      },
      {
        "code": "invoice_rejected",
        "status": 422,
        "message": "El programa no ha pogut emetre la factura: revisa el missatge."
      },
      {
        "code": "whatsapp_window_closed",
        "status": 422,
        "message": "Han passat més de 24 h des de l'últim missatge del client: per WhatsApp només se li pot escriure amb una plantilla aprovada (template_id)."
      },
      {
        "code": "channel_unavailable",
        "status": 409,
        "message": "Aquest canal no està configurat al taller."
      },
      {
        "code": "conversation_not_found",
        "status": 404,
        "message": "No existeix aquesta conversa."
      },
      {
        "code": "sandbox_only",
        "status": 404,
        "message": "Només disponible a l'entorn de proves (https://demo.programa-taller.es)."
      },
      {
        "code": "client_key_forbidden",
        "status": 403,
        "message": "Aquesta clau està limitada a un client del taller i no pot fer aquesta operació: només treballa amb les dades del seu client."
      }
    ]
  },
  "security": [
    {
      "bearerAuth": []
    },
    {
      "apiKeyAuth": []
    }
  ]
}
