{
  "openapi": "3.1.0",
  "info": {
    "title": "Lakshmi Courses — FileMaker API by Happy Brain",
    "version": "0.4.0",
    "description": "Contratto tecnico Happy Brain: anagrafiche centri, edizioni, listini completi, ordini/rimborsi dimostrativi e ricevute ACK. FileMaker avvia tutte le connessioni HTTPS. Un account per centro; nessuna capienza. Questa versione consente soltanto acquisti dimostrativi, senza movimento di denaro. Inviti e regole commerciali vanno collaudati con Lakshmi prima dell’uso reale. Prezzi e persone negli esempi sono fittizi. Non inserire credenziali nella documentazione.",
    "contact": {
      "name": "Happy Brain"
    }
  },
  "servers": [
    {
      "url": "https://staging-corsi.lakshmi.it/wp-json/lakshmi/v1",
      "description": "Staging: usare solo dati di prova concordati e credenziali tecniche fornite separatamente."
    }
  ],
  "tags": [
    {
      "name": "Centres",
      "description": "Il codice identifica un centro estetico, non la singola partecipante."
    },
    {
      "name": "Courses",
      "description": "Archivio delle edizioni, distinto dalla successiva proiezione nel catalogo WooCommerce."
    },
    {
      "name": "Prices",
      "description": "Snapshot EUR completi, versionati e attivati atomicamente."
    },
    {
      "name": "Events",
      "description": "Coda durevole di fatti dimostrativi: lettura non consumante e ACK dopo il commit FileMaker."
    }
  ],
  "paths": {
    "/sync/customers": {
      "post": {
        "operationId": "syncCustomers",
        "tags": [
          "Centres"
        ],
        "summary": "Crea o aggiorna anagrafiche complete dei centri",
        "description": "Massimo 100 record e 262144 byte di corpo JSON. Ogni elemento è una transazione indipendente. HTTP 200 non garantisce il successo di tutti gli elementi: controllare results[].outcome. Codici ripetuti nello stesso lotto sono elaborati in ordine. Invio stessa revisione/contenuto normalizzato: unchanged; revisione più vecchia o stessa revisione con contenuto diverso: errore 409 per elemento. La sincronizzazione non crea né collega account. Nessuna connessione viene avviata verso FileMaker.",
        "security": [
          {
            "fileMakerApplicationPassword": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CustomerBatch"
              },
              "example": {
                "customers": [
                  {
                    "filemaker_code": "DEMO-FM-000123",
                    "name": "DEMO Centro Aurora",
                    "email": "aurora@example.test",
                    "price_tier": "demo_contracted",
                    "is_contractor": true,
                    "enabled": true,
                    "revision": 1
                  }
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Lotto elaborato. Verificare ogni risultato, inclusi eventuali errori 400/409/503 annidati.",
            "headers": {
              "Cache-Control": {
                "schema": {
                  "type": "string"
                },
                "example": "no-store, private"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/BatchResponse"
                },
                "examples": {
                  "created": {
                    "summary": "Centro creato, non ancora collegato a un account",
                    "value": {
                      "results": [
                        {
                          "index": 0,
                          "filemaker_code": "DEMO-FM-000123",
                          "outcome": "created",
                          "centre": {
                            "id": 42,
                            "filemaker_code": "DEMO-FM-000123",
                            "user_id": null,
                            "name": "DEMO Centro Aurora",
                            "email": "aurora@example.test",
                            "price_tier": "demo_contracted",
                            "is_contractor": true,
                            "enabled": true,
                            "revision": 1,
                            "created_at": "2026-09-10 12:00:00",
                            "updated_at": "2026-09-10 12:00:00"
                          }
                        }
                      ]
                    }
                  },
                  "conflict": {
                    "summary": "HTTP 200 con un errore per elemento",
                    "value": {
                      "results": [
                        {
                          "index": 0,
                          "filemaker_code": "DEMO-FM-000123",
                          "outcome": "error",
                          "error": {
                            "code": "lakshmi_stale_revision",
                            "message": "A newer centre revision is already stored.",
                            "status": 409
                          }
                        }
                      ]
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "413": {
            "$ref": "#/components/responses/TooLarge"
          },
          "415": {
            "$ref": "#/components/responses/UnsupportedMediaType"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable"
          }
        }
      }
    },
    "/sync/courses": {
      "post": {
        "operationId": "syncCourses",
        "tags": [
          "Courses"
        ],
        "summary": "Crea o aggiorna edizioni complete nell’archivio di integrazione",
        "description": "Massimo 100 record e 262144 byte JSON. Il codice identifica un’edizione, anche su più giornate, distinta dal corso di riferimento. Transazioni indipendenti e risultati per elemento; HTTP 200 non implica tutti acquisiti. Revisioni crescenti per edizione, retry identici unchanged. Titolo e sede trimmati, date ordinate prima del confronto. Non crea prodotti WooCommerce, non pubblica corsi, non calcola prezzi, non aggiorna ordini o utenti. Nessuna capienza. Orari e sedi per singolo incontro non sono ancora supportati.",
        "security": [
          {
            "fileMakerApplicationPassword": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CourseBatch"
              },
              "example": {
                "courses": [
                  {
                    "filemaker_code": "DEMO-ED-000001",
                    "course_code": "DEMO-COURSE-001",
                    "title": "DEMO Rituale viso",
                    "dates": [
                      "2026-11-09",
                      "2026-11-10"
                    ],
                    "location": "DEMO Training Centre",
                    "enabled": true,
                    "revision": 1
                  }
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Lotto elaborato; verificare ogni outcome. Errori di validazione lakshmi_course_* 400 (o lakshmi_invalid_record per elementi non oggetto), conflitti lakshmi_course_revision_conflict/lakshmi_course_stale_revision 409, storage lakshmi_course_storage_unavailable 503.",
            "headers": {
              "Cache-Control": {
                "schema": {
                  "type": "string"
                },
                "example": "no-store, private"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CourseBatchResponse"
                },
                "examples": {
                  "created": {
                    "summary": "Edizione acquisita, nessun prodotto creato",
                    "value": {
                      "results": [
                        {
                          "index": 0,
                          "filemaker_code": "DEMO-ED-000001",
                          "outcome": "created",
                          "edition": {
                            "id": 12,
                            "filemaker_code": "DEMO-ED-000001",
                            "course_code": "DEMO-COURSE-001",
                            "title": "DEMO Rituale viso",
                            "dates": [
                              "2026-11-09",
                              "2026-11-10"
                            ],
                            "location": "DEMO Training Centre",
                            "enabled": true,
                            "revision": 1,
                            "created_at": "2026-09-10 12:00:00",
                            "updated_at": "2026-09-10 12:00:00"
                          }
                        }
                      ]
                    }
                  },
                  "conflict": {
                    "summary": "Revisione vecchia rifiutata",
                    "value": {
                      "results": [
                        {
                          "index": 0,
                          "filemaker_code": "DEMO-ED-000001",
                          "outcome": "error",
                          "error": {
                            "code": "lakshmi_course_stale_revision",
                            "message": "A newer course edition revision is already stored.",
                            "status": 409
                          }
                        }
                      ]
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "413": {
            "$ref": "#/components/responses/TooLarge"
          },
          "415": {
            "$ref": "#/components/responses/UnsupportedMediaType"
          },
          "503": {
            "description": "Archivio edizioni non pronto: lakshmi_courses_unavailable. Non confondere con un errore per elemento dentro HTTP 200.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WordPressError"
                },
                "example": {
                  "code": "lakshmi_courses_unavailable",
                  "message": "Course synchronization is not ready.",
                  "data": {
                    "status": 503
                  }
                }
              }
            }
          }
        }
      }
    },
    "/sync/price-lists": {
      "post": {
        "operationId": "syncPriceList",
        "tags": [
          "Prices"
        ],
        "summary": "Attiva uno snapshot completo del listino EUR",
        "description": "Un singolo oggetto listino, non un array né un batch. Massimo 500 offerte e 256 KiB. Validazione totale e attivazione atomica: nessun listino parzialmente valido. Il primo list_code diventa fisso; revisioni immutabili crescenti. Stessa revisione/contenuto normalizzato=>unchanged; conflitto o revisione obsoleta=> HTTP 409. La proiezione WooCommerce è successiva e distinta: catalogue.pending non annulla il listino salvato. Ripetere lo stesso snapshot per riprovare. Nessun prezzo di fascia è scritto come prezzo pubblico.",
        "security": [
          {
            "fileMakerApplicationPassword": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/PriceListInput"
              },
              "example": {
                "list_code": "DEMO-LIST-001",
                "revision": 1,
                "currency": "EUR",
                "enabled": true,
                "offers": [
                  {
                    "edition_code": "DEMO-ED-000001",
                    "price_tier": "demo_standard",
                    "ticket_type": "single",
                    "amount_minor": 12000,
                    "included_participants": 1,
                    "extra_participant_minor": null
                  },
                  {
                    "edition_code": "DEMO-ED-000001",
                    "price_tier": "demo_standard",
                    "ticket_type": "centre_package",
                    "amount_minor": 20000,
                    "included_participants": 2,
                    "extra_participant_minor": 8000
                  },
                  {
                    "edition_code": "DEMO-ED-000001",
                    "price_tier": "demo_contracted",
                    "ticket_type": "single",
                    "amount_minor": 9000,
                    "included_participants": 1,
                    "extra_participant_minor": null
                  },
                  {
                    "edition_code": "DEMO-ED-000001",
                    "price_tier": "demo_contracted",
                    "ticket_type": "centre_package",
                    "amount_minor": 15000,
                    "included_participants": 2,
                    "extra_participant_minor": 6500
                  },
                  {
                    "edition_code": "DEMO-ED-000002",
                    "price_tier": "demo_standard",
                    "ticket_type": "single",
                    "amount_minor": 14000,
                    "included_participants": 1,
                    "extra_participant_minor": null
                  },
                  {
                    "edition_code": "DEMO-ED-000002",
                    "price_tier": "demo_standard",
                    "ticket_type": "centre_package",
                    "amount_minor": 23500,
                    "included_participants": 2,
                    "extra_participant_minor": 8000
                  },
                  {
                    "edition_code": "DEMO-ED-000002",
                    "price_tier": "demo_contracted",
                    "ticket_type": "single",
                    "amount_minor": 11000,
                    "included_participants": 1,
                    "extra_participant_minor": null
                  },
                  {
                    "edition_code": "DEMO-ED-000002",
                    "price_tier": "demo_contracted",
                    "ticket_type": "centre_package",
                    "amount_minor": 18500,
                    "included_participants": 2,
                    "extra_participant_minor": 6500
                  },
                  {
                    "edition_code": "DEMO-ED-000004",
                    "price_tier": "demo_standard",
                    "ticket_type": "single",
                    "amount_minor": 18000,
                    "included_participants": 1,
                    "extra_participant_minor": null
                  },
                  {
                    "edition_code": "DEMO-ED-000004",
                    "price_tier": "demo_standard",
                    "ticket_type": "centre_package",
                    "amount_minor": 30500,
                    "included_participants": 2,
                    "extra_participant_minor": 8000
                  },
                  {
                    "edition_code": "DEMO-ED-000004",
                    "price_tier": "demo_contracted",
                    "ticket_type": "single",
                    "amount_minor": 15000,
                    "included_participants": 1,
                    "extra_participant_minor": null
                  },
                  {
                    "edition_code": "DEMO-ED-000004",
                    "price_tier": "demo_contracted",
                    "ticket_type": "centre_package",
                    "amount_minor": 25500,
                    "included_participants": 2,
                    "extra_participant_minor": 6500
                  },
                  {
                    "edition_code": "DEMO-ED-000005",
                    "price_tier": "demo_standard",
                    "ticket_type": "single",
                    "amount_minor": 20000,
                    "included_participants": 1,
                    "extra_participant_minor": null
                  },
                  {
                    "edition_code": "DEMO-ED-000005",
                    "price_tier": "demo_standard",
                    "ticket_type": "centre_package",
                    "amount_minor": 34000,
                    "included_participants": 2,
                    "extra_participant_minor": 8000
                  },
                  {
                    "edition_code": "DEMO-ED-000005",
                    "price_tier": "demo_contracted",
                    "ticket_type": "single",
                    "amount_minor": 17000,
                    "included_participants": 1,
                    "extra_participant_minor": null
                  },
                  {
                    "edition_code": "DEMO-ED-000005",
                    "price_tier": "demo_contracted",
                    "ticket_type": "centre_package",
                    "amount_minor": 29000,
                    "included_participants": 2,
                    "extra_participant_minor": 6500
                  },
                  {
                    "edition_code": "DEMO-ED-000006",
                    "price_tier": "demo_standard",
                    "ticket_type": "single",
                    "amount_minor": 22000,
                    "included_participants": 1,
                    "extra_participant_minor": null
                  },
                  {
                    "edition_code": "DEMO-ED-000006",
                    "price_tier": "demo_standard",
                    "ticket_type": "centre_package",
                    "amount_minor": 37500,
                    "included_participants": 2,
                    "extra_participant_minor": 8000
                  },
                  {
                    "edition_code": "DEMO-ED-000006",
                    "price_tier": "demo_contracted",
                    "ticket_type": "single",
                    "amount_minor": 19000,
                    "included_participants": 1,
                    "extra_participant_minor": null
                  },
                  {
                    "edition_code": "DEMO-ED-000006",
                    "price_tier": "demo_contracted",
                    "ticket_type": "centre_package",
                    "amount_minor": 32500,
                    "included_participants": 2,
                    "extra_participant_minor": 6500
                  },
                  {
                    "edition_code": "DEMO-ED-000007",
                    "price_tier": "demo_standard",
                    "ticket_type": "single",
                    "amount_minor": 24000,
                    "included_participants": 1,
                    "extra_participant_minor": null
                  },
                  {
                    "edition_code": "DEMO-ED-000007",
                    "price_tier": "demo_standard",
                    "ticket_type": "centre_package",
                    "amount_minor": 41000,
                    "included_participants": 2,
                    "extra_participant_minor": 8000
                  },
                  {
                    "edition_code": "DEMO-ED-000007",
                    "price_tier": "demo_contracted",
                    "ticket_type": "single",
                    "amount_minor": 21000,
                    "included_participants": 1,
                    "extra_participant_minor": null
                  },
                  {
                    "edition_code": "DEMO-ED-000007",
                    "price_tier": "demo_contracted",
                    "ticket_type": "centre_package",
                    "amount_minor": 36000,
                    "included_participants": 2,
                    "extra_participant_minor": 6500
                  }
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Listino salvato; controllare separatamente price_list.outcome e catalogue.outcome.",
            "headers": {
              "Cache-Control": {
                "schema": {
                  "type": "string"
                },
                "example": "no-store, private"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PriceListResponse"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "409": {
            "description": "Codice listino diverso, revisione obsoleta o stessa revisione con contenuto differente.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PriceError"
                }
              }
            }
          },
          "413": {
            "$ref": "#/components/responses/TooLarge"
          },
          "415": {
            "$ref": "#/components/responses/UnsupportedMediaType"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable"
          }
        }
      }
    },
    "/events": {
      "get": {
        "operationId": "getPendingEvents",
        "tags": [
          "Events"
        ],
        "summary": "Legge gli eventi più vecchi ancora senza ACK",
        "description": "Ogni GET è iniziata da FileMaker. Lettura non consumante, senza prenotazione o cancellazione; due lettori possono ricevere gli stessi event_id. Salvare idempotentemente e confermare la transazione in FileMaker prima dell’ACK. Dopo ACK della pagina, ripetere GET per gli eventi successivi. Nessun parametro cursor/after/offset: parametri extra rifiutati con 400. Errori di archivio rispondono con 503, mai una falsa coda vuota. Eventi attuali soltanto demo, senza movimento di denaro.",
        "security": [
          {
            "fileMakerApplicationPassword": []
          }
        ],
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 100
            },
            "description": "Rappresentazione decimale 1..100, senza zeri iniziali. Unico parametro ammesso."
          }
        ],
        "responses": {
          "200": {
            "description": "Pagina non consumata; gli stessi eventi restano disponibili fino ad ACK.",
            "headers": {
              "Cache-Control": {
                "schema": {
                  "type": "string"
                },
                "example": "no-store, private"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PendingEvents"
                },
                "example": {
                  "events": [
                    {
                      "sequence": 1,
                      "event_id": "11111111-1111-4111-8111-111111111111",
                      "event_type": "order.paid",
                      "order_id": 9001,
                      "payload": {
                        "schema_version": 1,
                        "demo": true,
                        "money_moved": false,
                        "order_id": 9001,
                        "centre": {
                          "filemaker_code": "DEMO-000001",
                          "revision": 1,
                          "name": "DEMO Centro Aurora",
                          "email": "contracted@example.test",
                          "price_tier": "demo_contracted",
                          "is_contractor": true
                        },
                        "currency": "EUR",
                        "paid_at": "2026-09-10T15:00:00Z",
                        "payment_method": "lakshmi_demo",
                        "transaction_id": "DEMO-9001-33333333-3333-4333-8333-333333333333",
                        "total_minor": 25000,
                        "tax_minor": 0,
                        "discount_minor": 0,
                        "shipping_minor": 0,
                        "lines": [
                          {
                            "order_item_id": 7001,
                            "product_id": 8001,
                            "quantity": 1,
                            "edition": {
                              "filemaker_code": "DEMO-ED-000002",
                              "course_code": "DEMO-COURSE-001",
                              "revision": 1,
                              "title": "DEMO Rituale viso",
                              "dates": [
                                "2026-12-01"
                              ],
                              "location": "DEMO Training Centre"
                            },
                            "quote": {
                              "list_code": "DEMO-LIST-001",
                              "list_revision": 1,
                              "currency": "EUR",
                              "edition_code": "DEMO-ED-000002",
                              "price_tier": "demo_contracted",
                              "ticket_type": "centre_package",
                              "participant_count": 3,
                              "included_count": 2,
                              "extra_count": 1,
                              "amount_minor": 18500,
                              "extra_participant_minor": 6500,
                              "total_minor": 25000
                            },
                            "participants": [
                              {
                                "first_name": "Nina",
                                "last_name": "Demo"
                              },
                              {
                                "first_name": "Mira",
                                "last_name": "Demo"
                              },
                              {
                                "first_name": "Lia",
                                "last_name": "Demo"
                              }
                            ],
                            "subtotal_minor": 25000,
                            "total_minor": 25000,
                            "tax_minor": 0
                          }
                        ]
                      },
                      "created_at": "2026-09-10 15:00:00",
                      "acknowledged_at": null,
                      "filemaker_id": null
                    },
                    {
                      "sequence": 2,
                      "event_id": "22222222-2222-4222-8222-222222222222",
                      "event_type": "order.refunded",
                      "order_id": 9001,
                      "payload": {
                        "schema_version": 1,
                        "demo": true,
                        "money_moved": false,
                        "order_id": 9001,
                        "refund_id": 9002,
                        "paid_event_id": "11111111-1111-4111-8111-111111111111",
                        "centre": {
                          "filemaker_code": "DEMO-000001",
                          "revision": 1,
                          "name": "DEMO Centro Aurora",
                          "email": "contracted@example.test",
                          "price_tier": "demo_contracted",
                          "is_contractor": true
                        },
                        "currency": "EUR",
                        "refunded_at": "2026-09-10T15:05:00Z",
                        "refund_method": "demo_gateway",
                        "amount_minor": 3000,
                        "tax_minor": 0,
                        "original_total_minor": 25000,
                        "lines": [],
                        "participant_cancellations": []
                      },
                      "created_at": "2026-09-10 15:05:00",
                      "acknowledged_at": null,
                      "filemaker_id": null
                    }
                  ],
                  "count": 2
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable"
          }
        }
      }
    },
    "/events/ack": {
      "post": {
        "operationId": "acknowledgeEvents",
        "tags": [
          "Events"
        ],
        "summary": "Conferma eventi già salvati definitivamente in FileMaker",
        "description": "Da 1 a 100 ricevute, massimo 256 KiB. Ogni elemento commette indipendentemente. Stesso event_id/filemaker_id=>unchanged; riferimento diverso per evento già confermato=>409 per elemento; UUID sconosciuto=>404. ACK non cancella l’evento. Correlare gli esiti per index e verificare acknowledged/unchanged prima di rimuovere la ricevuta dalla coda locale. Non confermare mai prima del commit FileMaker.",
        "security": [
          {
            "fileMakerApplicationPassword": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/AckBatch"
              },
              "example": {
                "acknowledgements": [
                  {
                    "event_id": "11111111-1111-4111-8111-111111111111",
                    "filemaker_id": "DEMO-FM-ORDER-9001"
                  },
                  {
                    "event_id": "22222222-2222-4222-8222-222222222222",
                    "filemaker_id": "DEMO-FM-REFUND-9002"
                  }
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Ricevute elaborate indipendentemente: HTTP200 può contenere errori per elemento.",
            "headers": {
              "Cache-Control": {
                "schema": {
                  "type": "string"
                },
                "example": "no-store, private"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AckResponse"
                },
                "example": {
                  "results": [
                    {
                      "index": 0,
                      "event_id": "11111111-1111-4111-8111-111111111111",
                      "filemaker_id": "DEMO-FM-ORDER-9001",
                      "outcome": "acknowledged",
                      "acknowledged_at": "2026-09-10 15:10:00"
                    },
                    {
                      "index": 1,
                      "event_id": "22222222-2222-4222-8222-222222222222",
                      "filemaker_id": "DEMO-FM-REFUND-9002",
                      "outcome": "acknowledged",
                      "acknowledged_at": "2026-09-10 15:10:00"
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "413": {
            "$ref": "#/components/responses/TooLarge"
          },
          "415": {
            "$ref": "#/components/responses/UnsupportedMediaType"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable"
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "fileMakerApplicationPassword": {
        "type": "http",
        "scheme": "basic",
        "description": "Username tecnico con ruolo lakshmi_integrator/capability lakshmi_sync e WordPress Application Password dedicata. Non usare la password ordinaria né cookie/nonce. HTTPS obbligatorio; credenziali fornite fuori da questa documentazione. Nessuna credenziale tecnica remota è creata automaticamente."
      }
    },
    "schemas": {
      "CourseInput": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "filemaker_code",
          "course_code",
          "title",
          "dates",
          "location",
          "enabled",
          "revision"
        ],
        "properties": {
          "filemaker_code": {
            "type": "string",
            "minLength": 1,
            "maxLength": 64,
            "pattern": "^[A-Za-z0-9._-]{1,64}$",
            "description": "Codice stabile dell’edizione, non del corso generale. Stringa ASCII case-sensitive con lettere, numeri, punto, underscore o trattino. Zeri iniziali preservati; nessun trim."
          },
          "course_code": {
            "type": "string",
            "minLength": 1,
            "maxLength": 64,
            "pattern": "^[A-Za-z0-9._-]{1,64}$",
            "description": "Codice del corso/percorso di riferimento, con le stesse regole del codice edizione. Più edizioni possono condividerlo. Non è richiesta l’esistenza di un record padre."
          },
          "title": {
            "type": "string",
            "minLength": 1,
            "maxLength": 200,
            "description": "Titolo dell’edizione, 1–200 caratteri Unicode dopo trim, senza caratteri di controllo. Non un contenuto HTML editoriale."
          },
          "dates": {
            "type": "array",
            "minItems": 1,
            "maxItems": 60,
            "uniqueItems": true,
            "items": {
              "type": "string",
              "format": "date",
              "pattern": "^[0-9]{4}-[0-9]{2}-[0-9]{2}$"
            },
            "description": "Array JSON di 1–60 date reali e distinte, YYYY-MM-DD (anno 0001–9999). Il server ordina le date in modo crescente. Nessun orario o fuso: sono giorni di calendario, non timestamp UTC. Duplicati, oggetti e date impossibili sono rifiutati."
          },
          "location": {
            "type": "string",
            "minLength": 1,
            "maxLength": 200,
            "description": "Sede comune alle giornate dell’edizione, 1–200 caratteri Unicode dopo trim, senza controlli. Orari o sedi differenti per singolo incontro richiedono una futura estensione."
          },
          "enabled": {
            "type": "boolean",
            "description": "Stato dell’edizione nel gestionale. Disabilitare blocca immediatamente nuovi preventivi/acquisti; la proiezione del catalogo resta distinta dall’intake."
          },
          "revision": {
            "type": "integer",
            "minimum": 1,
            "maximum": 9223372036854775807,
            "description": "Contatore positivo crescente per edizione, distinto da quello dei centri. Solo intero JSON: stringhe, 1.0 e 1e0 rifiutati. Consigliato <=9007199254740991 per interoperabilità."
          }
        }
      },
      "CourseBatch": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "courses"
        ],
        "x-max-body-bytes": 262144,
        "properties": {
          "courses": {
            "type": "array",
            "minItems": 1,
            "maxItems": 100,
            "items": {
              "$ref": "#/components/schemas/CourseInput"
            }
          }
        }
      },
      "CourseEdition": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "id",
          "filemaker_code",
          "course_code",
          "title",
          "dates",
          "location",
          "enabled",
          "revision",
          "created_at",
          "updated_at"
        ],
        "properties": {
          "id": {
            "type": "integer",
            "minimum": 1,
            "description": "ID locale dell’archivio edizioni, non ID prodotto WooCommerce."
          },
          "filemaker_code": {
            "$ref": "#/components/schemas/CourseInput/properties/filemaker_code"
          },
          "course_code": {
            "$ref": "#/components/schemas/CourseInput/properties/course_code"
          },
          "title": {
            "$ref": "#/components/schemas/CourseInput/properties/title"
          },
          "dates": {
            "$ref": "#/components/schemas/CourseInput/properties/dates"
          },
          "location": {
            "$ref": "#/components/schemas/CourseInput/properties/location"
          },
          "enabled": {
            "type": "boolean"
          },
          "revision": {
            "$ref": "#/components/schemas/CourseInput/properties/revision"
          },
          "created_at": {
            "$ref": "#/components/schemas/UtcTimestamp"
          },
          "updated_at": {
            "$ref": "#/components/schemas/UtcTimestamp"
          }
        }
      },
      "CourseSuccessResult": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "index",
          "filemaker_code",
          "outcome",
          "edition"
        ],
        "properties": {
          "index": {
            "type": "integer",
            "minimum": 0
          },
          "filemaker_code": {
            "type": "string"
          },
          "outcome": {
            "type": "string",
            "enum": [
              "created",
              "updated",
              "unchanged"
            ]
          },
          "edition": {
            "$ref": "#/components/schemas/CourseEdition"
          }
        }
      },
      "CourseBatchResponse": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "results"
        ],
        "properties": {
          "results": {
            "type": "array",
            "minItems": 1,
            "maxItems": 100,
            "items": {
              "oneOf": [
                {
                  "$ref": "#/components/schemas/CourseSuccessResult"
                },
                {
                  "$ref": "#/components/schemas/ErrorResult"
                }
              ]
            }
          }
        }
      },
      "CentreInput": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "filemaker_code",
          "name",
          "email",
          "price_tier",
          "is_contractor",
          "enabled",
          "revision"
        ],
        "properties": {
          "filemaker_code": {
            "type": "string",
            "minLength": 1,
            "maxLength": 64,
            "pattern": "^[A-Za-z0-9._-]{1,64}$",
            "description": "Identificatore testuale case-sensitive. Zeri iniziali preservati: 000123 e 123 sono distinti. Non inviare un numero."
          },
          "name": {
            "type": "string",
            "minLength": 1,
            "maxLength": 200,
            "pattern": "^[^\\u0000-\\u001F\\u007F]+$",
            "description": "Nome non vuoto dopo trim, massimo 200 caratteri; nessun carattere di controllo. Il server rimuove gli spazi iniziali/finali prima di validare la lunghezza."
          },
          "email": {
            "type": "string",
            "format": "email",
            "maxLength": 100,
            "description": "Email di riferimento valida, normalizzata con trim e conversione in minuscolo. Validazione effettiva WordPress is_email; nessuna verifica di proprietà o associazione automatica."
          },
          "price_tier": {
            "type": "string",
            "minLength": 1,
            "maxLength": 40,
            "pattern": "^[a-z0-9_-]{1,40}$",
            "description": "Codice commerciale, non un prezzo. Valori demo non rappresentano fasce di produzione concordate; nessun fallback automatico al listino standard."
          },
          "is_contractor": {
            "type": "boolean",
            "description": "Stato contrattista del centro. JSON true/false, non stringhe né 0/1."
          },
          "enabled": {
            "type": "boolean",
            "description": "Centro abilitato nel gestionale: il checkout dimostrativo richiede un account collegato, centro abilitato e fascia con un’offerta esatta."
          },
          "revision": {
            "type": "integer",
            "minimum": 1,
            "maximum": 9223372036854775807,
            "description": "Revisione positiva crescente per centro, accettata solo se decodificata come intero PHP. Numeric string e JSON 1.0/1e0 vengono rifiutati. Si raccomanda un contatore <=9007199254740991 per interoperabilità; il server supporta interi PHP a 64 bit."
          }
        }
      },
      "CustomerBatch": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "customers"
        ],
        "properties": {
          "customers": {
            "type": "array",
            "minItems": 1,
            "maxItems": 100,
            "items": {
              "$ref": "#/components/schemas/CentreInput"
            }
          }
        },
        "x-max-body-bytes": 262144
      },
      "Centre": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "id",
          "filemaker_code",
          "user_id",
          "name",
          "email",
          "price_tier",
          "is_contractor",
          "enabled",
          "revision",
          "created_at",
          "updated_at"
        ],
        "properties": {
          "id": {
            "type": "integer",
            "minimum": 1,
            "description": "Identificatore locale WordPress; non sostituisce il codice FileMaker."
          },
          "filemaker_code": {
            "$ref": "#/components/schemas/CentreInput/properties/filemaker_code"
          },
          "user_id": {
            "type": [
              "integer",
              "null"
            ],
            "minimum": 1,
            "description": "Account WooCommerce collegato, oppure null. Non può essere impostato tramite sync."
          },
          "name": {
            "$ref": "#/components/schemas/CentreInput/properties/name"
          },
          "email": {
            "$ref": "#/components/schemas/CentreInput/properties/email"
          },
          "price_tier": {
            "$ref": "#/components/schemas/CentreInput/properties/price_tier"
          },
          "is_contractor": {
            "type": "boolean"
          },
          "enabled": {
            "type": "boolean"
          },
          "revision": {
            "$ref": "#/components/schemas/CentreInput/properties/revision"
          },
          "created_at": {
            "$ref": "#/components/schemas/UtcTimestamp"
          },
          "updated_at": {
            "$ref": "#/components/schemas/UtcTimestamp"
          }
        }
      },
      "UtcTimestamp": {
        "type": "string",
        "pattern": "^\\d{4}-\\d{2}-\\d{2} \\d{2}:\\d{2}:\\d{2}$",
        "example": "2026-09-10 12:00:00",
        "description": "UTC, formato MySQL YYYY-MM-DD HH:mm:ss; non è RFC3339."
      },
      "SuccessResult": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "index",
          "filemaker_code",
          "outcome",
          "centre"
        ],
        "properties": {
          "index": {
            "type": "integer",
            "minimum": 0
          },
          "filemaker_code": {
            "type": "string"
          },
          "outcome": {
            "type": "string",
            "enum": [
              "created",
              "updated",
              "unchanged"
            ]
          },
          "centre": {
            "$ref": "#/components/schemas/Centre"
          }
        }
      },
      "ErrorResult": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "index",
          "filemaker_code",
          "outcome",
          "error"
        ],
        "properties": {
          "index": {
            "type": "integer",
            "minimum": 0
          },
          "filemaker_code": {
            "type": [
              "string",
              "null"
            ],
            "description": "Codice ricevuto troncato a 64 byte, oppure null se non disponibile. Per correlare un input invalido usare index."
          },
          "outcome": {
            "const": "error",
            "type": "string"
          },
          "error": {
            "type": "object",
            "additionalProperties": false,
            "required": [
              "code",
              "message",
              "status"
            ],
            "properties": {
              "code": {
                "type": "string"
              },
              "message": {
                "type": "string"
              },
              "status": {
                "type": "integer",
                "enum": [
                  400,
                  409,
                  503
                ]
              }
            }
          }
        }
      },
      "BatchResponse": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "results"
        ],
        "properties": {
          "results": {
            "type": "array",
            "minItems": 1,
            "maxItems": 100,
            "items": {
              "oneOf": [
                {
                  "$ref": "#/components/schemas/SuccessResult"
                },
                {
                  "$ref": "#/components/schemas/ErrorResult"
                }
              ]
            }
          }
        }
      },
      "WordPressError": {
        "type": "object",
        "required": [
          "code",
          "message",
          "data"
        ],
        "properties": {
          "code": {
            "type": "string"
          },
          "message": {
            "type": "string"
          },
          "data": {
            "type": "object",
            "required": [
              "status"
            ],
            "properties": {
              "status": {
                "type": "integer"
              }
            },
            "additionalProperties": true
          }
        },
        "additionalProperties": true
      },
      "PriceOffer": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "edition_code",
          "price_tier",
          "ticket_type",
          "amount_minor",
          "included_participants",
          "extra_participant_minor"
        ],
        "properties": {
          "edition_code": {
            "type": "string",
            "minLength": 1,
            "maxLength": 64,
            "pattern": "^[A-Za-z0-9._-]{1,64}$"
          },
          "price_tier": {
            "type": "string",
            "minLength": 1,
            "maxLength": 40,
            "pattern": "^[a-z0-9_-]{1,40}$"
          },
          "ticket_type": {
            "type": "string",
            "enum": [
              "single",
              "centre_package"
            ]
          },
          "amount_minor": {
            "type": "integer",
            "minimum": 1,
            "maximum": 100000000
          },
          "included_participants": {
            "type": "integer",
            "minimum": 1,
            "maximum": 20
          },
          "extra_participant_minor": {
            "type": [
              "integer",
              "null"
            ],
            "minimum": 1,
            "maximum": 100000000
          }
        },
        "allOf": [
          {
            "if": {
              "properties": {
                "ticket_type": {
                  "const": "single"
                }
              }
            },
            "then": {
              "properties": {
                "included_participants": {
                  "const": 1
                },
                "extra_participant_minor": {
                  "type": "null"
                }
              }
            }
          },
          {
            "if": {
              "properties": {
                "ticket_type": {
                  "const": "centre_package"
                }
              }
            },
            "then": {
              "properties": {
                "included_participants": {
                  "type": "integer",
                  "minimum": 2,
                  "maximum": 20
                }
              }
            }
          }
        ],
        "description": "Unica combinazione edition_code/price_tier/ticket_type. Importi in centesimi EUR, mai float. Pacchetto: quota base più extra; null vieta partecipanti aggiuntivi."
      },
      "PriceListInput": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "list_code",
          "revision",
          "currency",
          "enabled",
          "offers"
        ],
        "properties": {
          "list_code": {
            "type": "string",
            "minLength": 1,
            "maxLength": 64,
            "pattern": "^[A-Za-z0-9._-]{1,64}$",
            "description": "Il primo list_code accettato diventa fisso. Un altro codice viene rifiutato con 409; non esiste sostituzione implicita del listino."
          },
          "revision": {
            "type": "integer",
            "minimum": 1,
            "maximum": 9223372036854775807,
            "description": "Intero JSON positivo effettivo: stringhe, frazioni ed esponenti sono rifiutati. Revisioni immutabili e crescenti."
          },
          "currency": {
            "type": "string",
            "const": "EUR"
          },
          "enabled": {
            "type": "boolean"
          },
          "offers": {
            "type": "array",
            "minItems": 0,
            "maxItems": 500,
            "items": {
              "$ref": "#/components/schemas/PriceOffer"
            },
            "x-unique-by": [
              "edition_code",
              "price_tier",
              "ticket_type"
            ],
            "description": "Intero snapshot, non patch. Array vuoto ritira tutte le offerte. Tutte le edizioni devono già esistere; duplicate tuple o un’offerta invalida rifiutano l’intera richiesta."
          }
        },
        "x-max-body-bytes": 262144
      },
      "PriceListResult": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "list_code",
          "revision",
          "currency",
          "enabled",
          "offers",
          "created_at",
          "outcome"
        ],
        "properties": {
          "list_code": {
            "type": "string",
            "minLength": 1,
            "maxLength": 64,
            "pattern": "^[A-Za-z0-9._-]{1,64}$",
            "description": "Il primo list_code accettato diventa fisso. Un altro codice viene rifiutato con 409; non esiste sostituzione implicita del listino."
          },
          "revision": {
            "type": "integer",
            "minimum": 1,
            "maximum": 9223372036854775807,
            "description": "Intero JSON positivo effettivo: stringhe, frazioni ed esponenti sono rifiutati. Revisioni immutabili e crescenti."
          },
          "currency": {
            "type": "string",
            "const": "EUR"
          },
          "enabled": {
            "type": "boolean"
          },
          "offers": {
            "type": "array",
            "minItems": 0,
            "maxItems": 500,
            "items": {
              "$ref": "#/components/schemas/PriceOffer"
            },
            "x-unique-by": [
              "edition_code",
              "price_tier",
              "ticket_type"
            ],
            "description": "Intero snapshot, non patch. Array vuoto ritira tutte le offerte. Tutte le edizioni devono già esistere; duplicate tuple o un’offerta invalida rifiutano l’intera richiesta."
          },
          "created_at": {
            "$ref": "#/components/schemas/UtcTimestamp"
          },
          "outcome": {
            "type": "string",
            "enum": [
              "created",
              "updated",
              "unchanged"
            ]
          }
        }
      },
      "CatalogueProjection": {
        "oneOf": [
          {
            "type": "object",
            "additionalProperties": false,
            "required": [
              "outcome"
            ],
            "properties": {
              "outcome": {
                "const": "synchronized",
                "type": "string"
              }
            }
          },
          {
            "type": "object",
            "additionalProperties": false,
            "required": [
              "outcome",
              "message"
            ],
            "properties": {
              "outcome": {
                "const": "pending",
                "type": "string"
              },
              "message": {
                "type": "string"
              }
            }
          }
        ],
        "description": "Il listino è già salvato anche con pending. Ritentare lo stesso identico snapshot per riprovare la proiezione senza creare una revisione."
      },
      "PriceListResponse": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "price_list",
          "catalogue"
        ],
        "properties": {
          "price_list": {
            "$ref": "#/components/schemas/PriceListResult"
          },
          "catalogue": {
            "$ref": "#/components/schemas/CatalogueProjection"
          }
        }
      },
      "PriceError": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "code",
          "message",
          "data"
        ],
        "properties": {
          "code": {
            "type": "string",
            "enum": [
              "lakshmi_price_invalid_fields",
              "lakshmi_price_invalid_snapshot",
              "lakshmi_price_invalid_offer",
              "lakshmi_price_duplicate_offer",
              "lakshmi_price_unknown_edition",
              "lakshmi_price_list_code_conflict",
              "lakshmi_price_stale_revision",
              "lakshmi_price_revision_conflict",
              "lakshmi_price_storage_unavailable"
            ]
          },
          "message": {
            "type": "string"
          },
          "data": {
            "type": "object",
            "additionalProperties": false,
            "required": [
              "status"
            ],
            "properties": {
              "status": {
                "type": "integer",
                "enum": [
                  400,
                  409,
                  503
                ]
              }
            }
          }
        }
      },
      "PriceQuote": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "list_code",
          "list_revision",
          "currency",
          "edition_code",
          "price_tier",
          "ticket_type",
          "participant_count",
          "included_count",
          "extra_count",
          "amount_minor",
          "extra_participant_minor",
          "total_minor"
        ],
        "properties": {
          "list_code": {
            "type": "string",
            "minLength": 1,
            "maxLength": 64,
            "pattern": "^[A-Za-z0-9._-]{1,64}$"
          },
          "list_revision": {
            "type": "integer",
            "minimum": 1,
            "maximum": 9223372036854775807,
            "description": "Intero JSON positivo effettivo: stringhe, frazioni ed esponenti sono rifiutati. Revisioni immutabili e crescenti."
          },
          "currency": {
            "const": "EUR",
            "type": "string"
          },
          "edition_code": {
            "type": "string",
            "minLength": 1,
            "maxLength": 64,
            "pattern": "^[A-Za-z0-9._-]{1,64}$"
          },
          "price_tier": {
            "type": "string",
            "minLength": 1,
            "maxLength": 40,
            "pattern": "^[a-z0-9_-]{1,40}$"
          },
          "ticket_type": {
            "type": "string",
            "enum": [
              "single",
              "centre_package"
            ]
          },
          "participant_count": {
            "type": "integer",
            "minimum": 1,
            "maximum": 20
          },
          "included_count": {
            "type": "integer",
            "minimum": 1,
            "maximum": 20
          },
          "extra_count": {
            "type": "integer",
            "minimum": 0,
            "maximum": 18
          },
          "amount_minor": {
            "type": "integer",
            "minimum": 1,
            "maximum": 100000000
          },
          "extra_participant_minor": {
            "type": [
              "integer",
              "null"
            ],
            "minimum": 1,
            "maximum": 100000000
          },
          "total_minor": {
            "type": "integer",
            "minimum": 1,
            "maximum": 1900000000
          }
        },
        "description": "Snapshot accettato al checkout: single=1 partecipante; pacchetto da included_count a20. total_minor=amount_minor+extra_count*extra_participant_minor; null consente soltanto zero extra. Nessun fallback di fascia."
      },
      "CentreSnapshot": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "filemaker_code",
          "revision",
          "name",
          "email",
          "price_tier",
          "is_contractor"
        ],
        "properties": {
          "filemaker_code": {
            "type": "string",
            "minLength": 1,
            "maxLength": 64,
            "pattern": "^[A-Za-z0-9._-]{1,64}$"
          },
          "revision": {
            "type": "integer",
            "minimum": 1,
            "maximum": 9223372036854775807,
            "description": "Intero JSON positivo effettivo: stringhe, frazioni ed esponenti sono rifiutati. Revisioni immutabili e crescenti."
          },
          "name": {
            "type": "string",
            "minLength": 1,
            "maxLength": 200
          },
          "email": {
            "type": "string",
            "format": "email",
            "maxLength": 100
          },
          "price_tier": {
            "type": "string",
            "minLength": 1,
            "maxLength": 40,
            "pattern": "^[a-z0-9_-]{1,40}$"
          },
          "is_contractor": {
            "type": "boolean"
          }
        }
      },
      "EditionSnapshot": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "filemaker_code",
          "course_code",
          "revision",
          "title",
          "dates",
          "location"
        ],
        "properties": {
          "filemaker_code": {
            "type": "string",
            "minLength": 1,
            "maxLength": 64,
            "pattern": "^[A-Za-z0-9._-]{1,64}$"
          },
          "course_code": {
            "type": "string",
            "minLength": 1,
            "maxLength": 64,
            "pattern": "^[A-Za-z0-9._-]{1,64}$"
          },
          "revision": {
            "type": "integer",
            "minimum": 1,
            "maximum": 9223372036854775807,
            "description": "Intero JSON positivo effettivo: stringhe, frazioni ed esponenti sono rifiutati. Revisioni immutabili e crescenti."
          },
          "title": {
            "type": "string",
            "minLength": 1,
            "maxLength": 200
          },
          "dates": {
            "type": "array",
            "minItems": 1,
            "maxItems": 60,
            "uniqueItems": true,
            "items": {
              "type": "string",
              "format": "date",
              "pattern": "^[0-9]{4}-[0-9]{2}-[0-9]{2}$"
            },
            "description": "Array JSON di 1–60 date reali e distinte, YYYY-MM-DD (anno 0001–9999). Il server ordina le date in modo crescente. Nessun orario o fuso: sono giorni di calendario, non timestamp UTC. Duplicati, oggetti e date impossibili sono rifiutati."
          },
          "location": {
            "type": "string",
            "minLength": 1,
            "maxLength": 200
          }
        }
      },
      "Participant": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "first_name",
          "last_name"
        ],
        "properties": {
          "first_name": {
            "type": "string",
            "minLength": 1,
            "maxLength": 100,
            "pattern": "^[^<>\\u0000-\\u001F\\u007F]+$"
          },
          "last_name": {
            "type": "string",
            "minLength": 1,
            "maxLength": 100,
            "pattern": "^[^<>\\u0000-\\u001F\\u007F]+$"
          }
        }
      },
      "PaidLine": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "order_item_id",
          "product_id",
          "quantity",
          "edition",
          "quote",
          "participants",
          "subtotal_minor",
          "total_minor",
          "tax_minor"
        ],
        "properties": {
          "order_item_id": {
            "type": "integer",
            "minimum": 1
          },
          "product_id": {
            "type": "integer",
            "minimum": 1
          },
          "quantity": {
            "type": "integer",
            "const": 1
          },
          "edition": {
            "$ref": "#/components/schemas/EditionSnapshot"
          },
          "quote": {
            "$ref": "#/components/schemas/PriceQuote"
          },
          "participants": {
            "type": "array",
            "minItems": 1,
            "maxItems": 20,
            "items": {
              "$ref": "#/components/schemas/Participant"
            }
          },
          "subtotal_minor": {
            "type": "integer",
            "minimum": 0
          },
          "total_minor": {
            "type": "integer",
            "minimum": 0
          },
          "tax_minor": {
            "type": "integer",
            "minimum": 0
          }
        }
      },
      "PaidPayload": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "schema_version",
          "demo",
          "money_moved",
          "order_id",
          "centre",
          "currency",
          "paid_at",
          "payment_method",
          "transaction_id",
          "total_minor",
          "tax_minor",
          "discount_minor",
          "shipping_minor",
          "lines"
        ],
        "properties": {
          "schema_version": {
            "type": "integer",
            "const": 1
          },
          "demo": {
            "type": "boolean",
            "const": true
          },
          "money_moved": {
            "type": "boolean",
            "const": false
          },
          "order_id": {
            "type": "integer",
            "minimum": 1
          },
          "centre": {
            "$ref": "#/components/schemas/CentreSnapshot"
          },
          "currency": {
            "type": "string",
            "const": "EUR"
          },
          "paid_at": {
            "type": "string",
            "format": "date-time",
            "pattern": "Z$"
          },
          "payment_method": {
            "type": "string",
            "const": "lakshmi_demo"
          },
          "transaction_id": {
            "type": "string",
            "pattern": "^DEMO-"
          },
          "total_minor": {
            "type": "integer",
            "minimum": 1
          },
          "tax_minor": {
            "type": "integer",
            "minimum": 0
          },
          "discount_minor": {
            "type": "integer",
            "minimum": 0
          },
          "shipping_minor": {
            "type": "integer",
            "minimum": 0
          },
          "lines": {
            "type": "array",
            "minItems": 1,
            "items": {
              "$ref": "#/components/schemas/PaidLine"
            }
          }
        },
        "description": "Fatto dimostrativo dopo pagamento simulato riuscito. Importi interi in centesimi; snapshot del centro/edizione/listino/partecipanti non ricalcolati usando dati correnti."
      },
      "RefundLine": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "refund_item_id",
          "order_item_id",
          "item_type",
          "total_minor",
          "tax_minor"
        ],
        "properties": {
          "refund_item_id": {
            "type": "integer",
            "minimum": 1
          },
          "order_item_id": {
            "type": "integer",
            "minimum": 0
          },
          "item_type": {
            "type": "string",
            "enum": [
              "line_item",
              "fee",
              "shipping"
            ]
          },
          "total_minor": {
            "type": "integer",
            "minimum": 0
          },
          "tax_minor": {
            "type": "integer",
            "minimum": 0
          },
          "quantity": {
            "type": "integer",
            "minimum": 0,
            "maximum": 9999999
          }
        },
        "allOf": [
          {
            "if": {
              "properties": {
                "item_type": {
                  "const": "line_item"
                }
              }
            },
            "then": {
              "required": [
                "quantity"
              ]
            },
            "else": {
              "not": {
                "required": [
                  "quantity"
                ]
              }
            }
          }
        ]
      },
      "RefundPayload": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "schema_version",
          "demo",
          "money_moved",
          "order_id",
          "centre",
          "currency",
          "refund_id",
          "paid_event_id",
          "refunded_at",
          "refund_method",
          "amount_minor",
          "tax_minor",
          "original_total_minor",
          "lines",
          "participant_cancellations"
        ],
        "properties": {
          "schema_version": {
            "type": "integer",
            "const": 1
          },
          "demo": {
            "type": "boolean",
            "const": true
          },
          "money_moved": {
            "type": "boolean",
            "const": false
          },
          "order_id": {
            "type": "integer",
            "minimum": 1
          },
          "centre": {
            "$ref": "#/components/schemas/CentreSnapshot"
          },
          "currency": {
            "type": "string",
            "const": "EUR"
          },
          "refund_id": {
            "type": "integer",
            "minimum": 1
          },
          "paid_event_id": {
            "type": "string",
            "format": "uuid",
            "pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$"
          },
          "refunded_at": {
            "type": "string",
            "format": "date-time",
            "pattern": "Z$"
          },
          "refund_method": {
            "type": "string",
            "enum": [
              "demo_gateway",
              "manual_record"
            ]
          },
          "amount_minor": {
            "type": "integer",
            "minimum": 1
          },
          "tax_minor": {
            "type": "integer",
            "minimum": 0
          },
          "original_total_minor": {
            "type": "integer",
            "minimum": 1
          },
          "lines": {
            "type": "array",
            "minItems": 0,
            "items": {
              "$ref": "#/components/schemas/RefundLine"
            }
          },
          "participant_cancellations": {
            "type": "array",
            "maxItems": 0
          }
        },
        "description": "Rimborso finanziario dimostrativo, anche parziale o solo importo. amount_minor positivo; un evento per rimborso. Le righe possono essere vuote e participant_cancellations è sempre vuoto: non dedurre chi annullare."
      },
      "OutboxEvent": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "sequence",
          "event_id",
          "event_type",
          "order_id",
          "payload",
          "created_at",
          "acknowledged_at",
          "filemaker_id"
        ],
        "properties": {
          "sequence": {
            "type": "integer",
            "minimum": 1
          },
          "event_id": {
            "type": "string",
            "format": "uuid",
            "pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$"
          },
          "event_type": {
            "type": "string",
            "enum": [
              "order.paid",
              "order.refunded"
            ]
          },
          "order_id": {
            "type": "integer",
            "minimum": 1
          },
          "payload": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/PaidPayload"
              },
              {
                "$ref": "#/components/schemas/RefundPayload"
              }
            ]
          },
          "created_at": {
            "$ref": "#/components/schemas/UtcTimestamp"
          },
          "acknowledged_at": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/UtcTimestamp"
              },
              {
                "type": "null"
              }
            ]
          },
          "filemaker_id": {
            "anyOf": [
              {
                "type": "string",
                "minLength": 1,
                "maxLength": 128,
                "pattern": "^[A-Za-z0-9._:-]{1,128}$",
                "description": "Riferimento testuale FileMaker persistito, case-sensitive; non una credenziale."
              },
              {
                "type": "null"
              }
            ]
          }
        },
        "allOf": [
          {
            "if": {
              "properties": {
                "event_type": {
                  "const": "order.paid"
                }
              }
            },
            "then": {
              "properties": {
                "payload": {
                  "$ref": "#/components/schemas/PaidPayload"
                }
              }
            }
          },
          {
            "if": {
              "properties": {
                "event_type": {
                  "const": "order.refunded"
                }
              }
            },
            "then": {
              "properties": {
                "payload": {
                  "$ref": "#/components/schemas/RefundPayload"
                }
              }
            }
          }
        ]
      },
      "PendingEvents": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "events",
          "count"
        ],
        "properties": {
          "events": {
            "type": "array",
            "minItems": 0,
            "maxItems": 100,
            "items": {
              "$ref": "#/components/schemas/OutboxEvent"
            }
          },
          "count": {
            "type": "integer",
            "minimum": 0,
            "maximum": 100
          }
        },
        "description": "Pagina più vecchia non confermata, ordinata per sequence. Nessun cursor/after/offset. count coincide con events.length; zero è valido solo su una risposta HTTP200 decodificata correttamente."
      },
      "Acknowledgement": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "event_id",
          "filemaker_id"
        ],
        "properties": {
          "event_id": {
            "type": "string",
            "format": "uuid",
            "pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$"
          },
          "filemaker_id": {
            "type": "string",
            "minLength": 1,
            "maxLength": 128,
            "pattern": "^[A-Za-z0-9._:-]{1,128}$",
            "description": "Riferimento testuale FileMaker persistito, case-sensitive; non una credenziale."
          }
        }
      },
      "AckBatch": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "acknowledgements"
        ],
        "properties": {
          "acknowledgements": {
            "type": "array",
            "minItems": 1,
            "maxItems": 100,
            "items": {
              "$ref": "#/components/schemas/Acknowledgement"
            }
          }
        },
        "x-max-body-bytes": 262144
      },
      "AckSuccess": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "index",
          "event_id",
          "outcome",
          "filemaker_id",
          "acknowledged_at"
        ],
        "properties": {
          "index": {
            "type": "integer",
            "minimum": 0
          },
          "event_id": {
            "type": "string",
            "format": "uuid",
            "pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$"
          },
          "outcome": {
            "type": "string",
            "enum": [
              "acknowledged",
              "unchanged"
            ]
          },
          "filemaker_id": {
            "type": "string",
            "minLength": 1,
            "maxLength": 128,
            "pattern": "^[A-Za-z0-9._:-]{1,128}$",
            "description": "Riferimento testuale FileMaker persistito, case-sensitive; non una credenziale."
          },
          "acknowledged_at": {
            "$ref": "#/components/schemas/UtcTimestamp"
          }
        }
      },
      "AckError": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "index",
          "outcome",
          "error"
        ],
        "properties": {
          "index": {
            "type": "integer",
            "minimum": 0
          },
          "event_id": {
            "type": "string",
            "maxLength": 36,
            "description": "Diagnostico opzionale, non garantito UUID per input malformato."
          },
          "outcome": {
            "type": "string",
            "const": "error"
          },
          "error": {
            "type": "object",
            "additionalProperties": false,
            "required": [
              "code",
              "message",
              "status"
            ],
            "properties": {
              "code": {
                "type": "string",
                "enum": [
                  "lakshmi_event_invalid_ack",
                  "lakshmi_event_not_found",
                  "lakshmi_event_ack_conflict",
                  "lakshmi_event_storage_unavailable"
                ]
              },
              "message": {
                "type": "string"
              },
              "status": {
                "type": "integer",
                "enum": [
                  400,
                  404,
                  409,
                  503
                ]
              }
            }
          }
        }
      },
      "AckResponse": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "results"
        ],
        "properties": {
          "results": {
            "type": "array",
            "minItems": 1,
            "maxItems": 100,
            "items": {
              "oneOf": [
                {
                  "$ref": "#/components/schemas/AckSuccess"
                },
                {
                  "$ref": "#/components/schemas/AckError"
                }
              ]
            }
          }
        }
      }
    },
    "responses": {
      "BadRequest": {
        "description": "JSON/struttura/tipi non validi; su GET events, limit invalido o parametri non ammessi. WordPress può restituire rest_invalid_json prima del callback.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/WordPressError"
            },
            "example": {
              "code": "rest_invalid_json",
              "message": "Invalid JSON body passed.",
              "data": {
                "status": 400
              }
            }
          }
        }
      },
      "Unauthorized": {
        "description": "Credenziale Application Password assente, errata o revocata. I codici di errore nativi WordPress possono variare.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/WordPressError"
            },
            "example": {
              "code": "lakshmi_application_password_required",
              "message": "Use a dedicated integration Application Password.",
              "data": {
                "status": 401
              }
            }
          }
        }
      },
      "Forbidden": {
        "description": "HTTPS richiesto, metodo di autenticazione non ammesso oppure account senza lakshmi_sync.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/WordPressError"
            },
            "example": {
              "code": "lakshmi_sync_forbidden",
              "message": "This account cannot synchronize integration data.",
              "data": {
                "status": 403
              }
            }
          }
        }
      },
      "TooLarge": {
        "description": "Corpo superiore a 256 KiB. Proxy e WordPress possono rifiutare la richiesta prima del plugin.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/WordPressError"
            },
            "example": {
              "code": "lakshmi_batch_too_large",
              "message": "Maximum request size is 256 KiB.",
              "data": {
                "status": 413
              }
            }
          }
        }
      },
      "UnsupportedMediaType": {
        "description": "Content-Type non JSON.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/WordPressError"
            },
            "example": {
              "code": "lakshmi_json_required",
              "message": "Send application/json.",
              "data": {
                "status": 415
              }
            }
          }
        }
      },
      "Unavailable": {
        "description": "Archivio o integrazione non pronta/temporaneamente indisponibile. Un 503 events non significa coda vuota. Su batch centri/corsi/ACK gli errori di singolo elemento restano invece nel 200.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/WordPressError"
            },
            "example": {
              "code": "lakshmi_unavailable",
              "message": "Centre synchronization is not ready.",
              "data": {
                "status": 503
              }
            }
          }
        }
      }
    }
  }
}
