openapi: 3.1.0
info:
  title: Folia Interoperability API
  version: 1.0.0
  summary: API REST per pratiche comunali, pagoPA, documenti, protocollo e interoperabilità PDND-ready.
  license:
    name: Proprietary
servers:
  - url: https://api.folia.local/api/v1
security:
  - OAuth2: []
tags:
  - name: Auth
    description: Accesso SPID, CIE, ticket monouso e credenziali ente.
  - name: Tenants
    description: Provisioning e configurazione multi-tenant.
  - name: Operators
    description: Inviti, ruoli, stato account e sicurezza degli operatori ente.
  - name: Practices
    description: Presentazione e gestione workflow pratiche.
  - name: Payments
    description: Avvisi, callback e riconciliazione pagoPA.
  - name: Documents
    description: Documenti, versioning e download autorizzati.
  - name: Archive
    description: Consultazione read-only del database legacy di scansione archivistica.
  - name: Protocols
    description: Protocollo e sincronizzazione AOO.
  - name: Interop
    description: Catalogo e funzioni PDND-ready.
paths:
  /.well-known/openid-federation:
    get:
      tags: [Auth]
      security: []
      servers:
        - url: https://api.folia.local
      summary: Entity Configuration OIDC Federation del Relying Party
      responses:
        "200":
          description: Entity Statement JWT firmato
          content:
            application/entity-statement+jwt:
              schema:
                type: string
  /auth/providers:
    get:
      tags: [Auth]
      security: []
      summary: Provider di autenticazione disponibili
      parameters:
        - name: tenant
          in: query
          required: false
          schema:
            type: string
      responses:
        "200":
          description: Provider disponibili
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Envelope"
  /auth/spid/metadata:
    get:
      tags: [Auth]
      security: []
      summary: Endpoint SPID SAML dismesso
      responses:
        "410":
          description: Usare `/.well-known/openid-federation`
  /auth/spid/login:
    get:
      tags: [Auth]
      security: []
      summary: Avvia login SPID OIDC Federation
      parameters:
        - name: tenant
          in: query
          required: false
          schema:
            type: string
        - name: return_to
          in: query
          required: false
          description: URL di ritorno allowlistato. Web sotto `FOLIA_FRONTEND_URL`, app mobile `folia://auth/complete`.
          schema:
            type: string
            format: uri
        - name: as
          in: query
          required: false
          description: Usa `ente` per richiedere una sessione operatore comunale invece di cittadino.
          schema:
            enum: [utente, ente]
      responses:
        "302":
          description: Redirect verso Identity Provider SPID
  /auth/spid/acs:
    post:
      tags: [Auth]
      security: []
      summary: Endpoint SPID SAML dismesso
      responses:
        "410":
          description: Usare `/auth/spid/callback`
  /auth/spid/callback:
    get:
      tags: [Auth]
      security: []
      summary: Callback SPID OIDC Federation
      parameters:
        - name: code
          in: query
          required: true
          schema:
            type: string
        - name: state
          in: query
          required: true
          schema:
            type: string
        - name: iss
          in: query
          required: false
          schema:
            type: string
            format: uri
      responses:
        "302":
          description: Redirect verso frontend con ticket monouso
  /auth/cie/login:
    get:
      tags: [Auth]
      security: []
      summary: Avvia login CIE OIDC Federation
      parameters:
        - name: tenant
          in: query
          required: false
          schema:
            type: string
        - name: return_to
          in: query
          required: false
          description: URL di ritorno allowlistato. Web sotto `FOLIA_FRONTEND_URL`, app mobile `folia://auth/complete`.
          schema:
            type: string
            format: uri
        - name: as
          in: query
          required: false
          description: Usa `ente` per richiedere una sessione operatore comunale invece di cittadino.
          schema:
            enum: [utente, ente]
      responses:
        "302":
          description: Redirect verso provider CIE
  /auth/cie/callback:
    get:
      tags: [Auth]
      security: []
      summary: Callback CIE OIDC Federation
      parameters:
        - name: code
          in: query
          required: true
          schema:
            type: string
        - name: state
          in: query
          required: true
          schema:
            type: string
        - name: iss
          in: query
          required: true
          description: Issuer obbligatorio per mitigare mix-up attack.
          schema:
            type: string
            format: uri
      responses:
        "302":
          description: Redirect verso frontend con ticket monouso
  /auth/spid/proxy/logout:
    get:
      tags: [Auth]
      security: []
      summary: Chiude la sessione del proxy SAML SPID
      responses:
        "302":
          description: Redirect verso il proxy SAML o verso il frontend
  /auth/cie/proxy/logout:
    get:
      tags: [Auth]
      security: []
      summary: Chiude la sessione del proxy SAML CIE
      responses:
        "302":
          description: Redirect verso il proxy SAML o verso il frontend
  /auth/spid/logout:
    post:
      tags: [Auth]
      security: []
      summary: Revoca token SPID OIDC con private_key_jwt
      responses:
        "204":
          description: Token revocato o nessun token inviato
  /auth/cie/logout:
    post:
      tags: [Auth]
      security: []
      summary: Revoca token CIE OIDC con private_key_jwt
      responses:
        "204":
          description: Token revocato o nessun token inviato
  /auth/tickets/exchange:
    post:
      tags: [Auth]
      security: []
      summary: Scambia ticket monouso per token Passport
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/AuthTicketExchange"
      responses:
        "200":
          description: Token applicativo rilasciato
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Envelope"
  /auth/logout:
    post:
      tags: [Auth]
      summary: Chiude la sessione utente corrente
      description: Revoca i token SPID/CIE collegati, invalida il token Passport corrente e restituisce l'eventuale URL di logout del proxy SAML.
      responses:
        "200":
          description: Sessione revocata
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Envelope"
  /auth/ente/login:
    post:
      tags: [Auth]
      security: []
      summary: Login operatori comunali con credenziali ente e MFA
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/OperatorLogin"
      responses:
        "200":
          description: Ticket monouso rilasciato
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Envelope"
  /auth/ente/invitations/accept:
    post:
      tags: [Auth]
      security: []
      summary: Attiva un invito operatore monouso
      description: Imposta password, verifica email, accetta condizioni e abilita l'account operatore prima della configurazione MFA.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/OperatorInvitationAccept"
      responses:
        "200":
          description: Invito accettato
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Envelope"
  /operators:
    get:
      tags: [Operators]
      summary: Elenco operatori dell'ente
      description: Disponibile solo per admin ente, admin Folia e backoffice tecnico autorizzato.
      parameters:
        - $ref: "#/components/parameters/TenantId"
      responses:
        "200":
          description: Operatori, ruoli assegnabili e stato account
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Envelope"
  /operators/invitations:
    post:
      tags: [Operators]
      summary: Invita un operatore dell'ente
      parameters:
        - $ref: "#/components/parameters/TenantId"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/OperatorInvite"
      responses:
        "201":
          description: Operatore invitato
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Envelope"
  /operators/{operatorId}/invitations/resend:
    post:
      tags: [Operators]
      summary: Reinvia invito operatore
      parameters:
        - $ref: "#/components/parameters/TenantId"
        - $ref: "#/components/parameters/OperatorId"
      responses:
        "200":
          description: Invito reinviato
  /operators/{operatorId}/password-reset:
    post:
      tags: [Operators]
      summary: Invia reset password operatore
      parameters:
        - $ref: "#/components/parameters/TenantId"
        - $ref: "#/components/parameters/OperatorId"
      responses:
        "200":
          description: Email reset password inviata
  /operators/{operatorId}/mfa/reset:
    post:
      tags: [Operators]
      summary: Reset MFA operatore
      parameters:
        - $ref: "#/components/parameters/TenantId"
        - $ref: "#/components/parameters/OperatorId"
      responses:
        "200":
          description: MFA resettata
  /operators/{operatorId}/status:
    patch:
      tags: [Operators]
      summary: Disattiva o riattiva un operatore
      parameters:
        - $ref: "#/components/parameters/TenantId"
        - $ref: "#/components/parameters/OperatorId"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/OperatorStatusUpdate"
      responses:
        "200":
          description: Stato account aggiornato
  /interop/catalog:
    get:
      tags: [Interop]
      security: []
      summary: Catalogo API interoperabilità
      responses:
        "200":
          description: Catalogo disponibile
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Envelope"
  /tenants:
    get:
      tags: [Tenants]
      security: []
      summary: Elenco Comuni pubblici filtrabile
      parameters:
        - name: q
          in: query
          required: false
          schema:
            type: string
        - name: province
          in: query
          required: false
          schema:
            type: string
        - name: region
          in: query
          required: false
          schema:
            type: string
        - name: service
          in: query
          required: false
          schema:
            type: string
      responses:
        "200":
          description: Tenant
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Envelope"
    post:
      tags: [Tenants]
      security: []
      summary: Provisioning tenant amministrativo
      parameters:
        - $ref: "#/components/parameters/ProvisioningToken"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/TenantCreate"
      responses:
        "201":
          description: Tenant creato
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Envelope"
  /me:
    get:
      tags: [Auth]
      summary: Profilo utente autenticato e Comune corrente
      parameters:
        - $ref: "#/components/parameters/TenantId"
      responses:
        "200":
          description: Profilo utente
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Envelope"
  /me/device-tokens:
    post:
      tags: [Auth]
      summary: Registra token dispositivo mobile per notifiche push
      parameters:
        - $ref: "#/components/parameters/TenantId"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [platform, token]
              properties:
                platform:
                  type: string
                  enum: [ios, android]
                token:
                  type: string
                  maxLength: 512
                device_name:
                  type: string
      responses:
        "201":
          description: Token registrato
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Envelope"
  /practices:
    get:
      tags: [Practices]
      summary: Lista pratiche del tenant
      description: Per utenti cittadini restituisce esclusivamente le pratiche richieste dall'utente autenticato; la risposta include data richiesta, stato ricerca archivio, pagamenti, documenti e cronologia download auditabile.
      parameters:
        - $ref: "#/components/parameters/TenantId"
      responses:
        "200":
          description: Lista cursor-paginated
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Envelope"
    post:
      tags: [Practices]
      summary: Presenta una pratica
      parameters:
        - $ref: "#/components/parameters/TenantId"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/PracticeCreate"
      responses:
        "201":
          description: Pratica creata
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Envelope"
  /practices/{practiceId}/transition:
    patch:
      tags: [Practices]
      summary: Cambia stato pratica
      parameters:
        - $ref: "#/components/parameters/TenantId"
        - $ref: "#/components/parameters/PracticeId"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [status]
              properties:
                status:
                  enum: [submitted, pending_operator_review, assigned, in_review, payment_pending, payment_verified, validated, download_authorized, rejected, archived]
                reason:
                  type: string
      responses:
        "200":
          description: Stato aggiornato
  /practices/{practiceId}/archive-match:
    patch:
      tags: [Practices]
      summary: Abbina documento archivio e genera avviso pagoPA
      description: Usato dall'ente dopo la ricerca nel database storico. Con `found` collega l'unita archivistica alla pratica, genera l'avviso pagoPA legato al codice fiscale dell'utente e registra notifica email/push. Con `not_found` annulla la richiesta con motivazione.
      parameters:
        - $ref: "#/components/parameters/TenantId"
        - $ref: "#/components/parameters/PracticeId"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [decision]
              properties:
                decision:
                  enum: [found, not_found]
                archive_unit_id:
                  type: integer
                amount_cents:
                  type: integer
                  minimum: 50
                reason:
                  type: string
      responses:
        "200":
          description: Pratica aggiornata
  /practices/{practiceId}/payments/notices:
    post:
      tags: [Payments]
      summary: Genera avviso pagoPA
      parameters:
        - $ref: "#/components/parameters/TenantId"
        - $ref: "#/components/parameters/PracticeId"
        - $ref: "#/components/parameters/IdempotencyKey"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [amount_cents]
              properties:
                amount_cents:
                  type: integer
                  minimum: 50
      responses:
        "201":
          description: Avviso creato
  /payments/callbacks/pagopa:
    post:
      tags: [Payments]
      summary: Callback pagamento pagoPA firmata
      parameters:
        - $ref: "#/components/parameters/TenantId"
        - $ref: "#/components/parameters/IdempotencyKey"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [notice_number, status]
              properties:
                notice_number:
                  type: string
                status:
                  enum: [paid, failed, cancelled]
                paid_at:
                  type: string
                  format: date-time
      responses:
        "200":
          description: Callback accettata
  /payments/{paymentId}/reconcile:
    post:
      tags: [Payments]
      summary: Verifica stato pagamento pagoPA
      description: Consente all'utente o all'ente di chiedere la riconciliazione dello stato del pagamento prima dello sblocco del documento.
      parameters:
        - $ref: "#/components/parameters/TenantId"
        - name: paymentId
          in: path
          required: true
          schema:
            type: string
            format: uuid
      responses:
        "200":
          description: Stato pagamento aggiornato
  /practices/{practiceId}/documents:
    post:
      tags: [Documents]
      summary: Upload documento sicuro
      parameters:
        - $ref: "#/components/parameters/TenantId"
        - $ref: "#/components/parameters/PracticeId"
      requestBody:
        required: true
        content:
          multipart/form-data:
            schema:
              type: object
              required: [document_type, title, file]
              properties:
                document_type:
                  type: string
                title:
                  type: string
                file:
                  type: string
                  format: binary
      responses:
        "201":
          description: Documento acquisito
  /documents/{documentId}/authorize-download:
    post:
      tags: [Documents]
      summary: Autorizza download cittadino
      parameters:
        - $ref: "#/components/parameters/TenantId"
        - $ref: "#/components/parameters/DocumentId"
      responses:
        "200":
          description: Download autorizzato
  /archive/units:
    get:
      tags: [Archive]
      summary: Ricerca fascicoli dal database legacy di scansione
      parameters:
        - $ref: "#/components/parameters/TenantId"
        - name: q
          in: query
          required: false
          schema:
            type: string
        - name: busta
          in: query
          required: false
          schema:
            type: string
        - name: fascicolo
          in: query
          required: false
          schema:
            type: string
        - name: protocollo
          in: query
          required: false
          schema:
            type: string
        - name: per_page
          in: query
          required: false
          schema:
            type: integer
            minimum: 1
            maximum: 100
      responses:
        "200":
          description: Risultati archivio legacy
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Envelope"
  /archive/units/{unitId}:
    get:
      tags: [Archive]
      summary: Dettaglio fascicolo dal database legacy di scansione
      parameters:
        - $ref: "#/components/parameters/TenantId"
        - name: unitId
          in: path
          required: true
          schema:
            type: integer
      responses:
        "200":
          description: Dettaglio archivio legacy
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Envelope"
  /practices/{practiceId}/protocols:
    post:
      tags: [Protocols]
      summary: Registra protocollo
      parameters:
        - $ref: "#/components/parameters/TenantId"
        - $ref: "#/components/parameters/PracticeId"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/ProtocolCreate"
      responses:
        "201":
          description: Protocollo registrato
components:
  securitySchemes:
    OAuth2:
      type: oauth2
      flows:
        authorizationCode:
          authorizationUrl: /oauth/authorize
          tokenUrl: /oauth/token
          scopes:
            practices:read: Lettura pratiche
            practices:write: Scrittura pratiche
            payments:write: Gestione pagamenti
            documents:read: Lettura documenti
            documents:write: Scrittura documenti
            protocols:write: Protocollo
            interop:sync: Interoperabilità
            archive:read: Consultazione archivio legacy
        clientCredentials:
          tokenUrl: /oauth/token
          scopes:
            interop:sync: Interoperabilità machine-to-machine
  parameters:
    TenantId:
      name: X-Tenant-ID
      in: header
      required: true
      schema:
        type: string
        format: uuid
    IdempotencyKey:
      name: Idempotency-Key
      in: header
      required: true
      schema:
        type: string
        minLength: 16
    ProvisioningToken:
      name: X-Provisioning-Token
      in: header
      required: true
      schema:
        type: string
        minLength: 32
    PracticeId:
      name: practiceId
      in: path
      required: true
      schema:
        type: string
        format: uuid
    DocumentId:
      name: documentId
      in: path
      required: true
      schema:
        type: string
        format: uuid
    OperatorId:
      name: operatorId
      in: path
      required: true
      schema:
        type: integer
  schemas:
    Envelope:
      type: object
      required: [data]
      properties:
        data: true
    TenantCreate:
      type: object
      required: [code, name]
      properties:
        code:
          type: string
        ipa_code:
          type: string
        fiscal_code:
          type: string
        name:
          type: string
        domains:
          type: array
          items:
            type: string
        spid_metadata_url:
          type: string
          format: uri
        cie_client_id:
          type: string
        pagopa_broker_code:
          type: string
    PracticeCreate:
      type: object
      required: [type, subject, payload]
      properties:
        type:
          enum: [SUE, SUAP, ANAGRAFE, CERTIFICATI_STORICI, EDILIZIA, TRIBUTI, AMMINISTRATIVA]
        subject:
          type: string
        form_version:
          type: string
        payload:
          oneOf:
            - $ref: "#/components/schemas/ArchiveDocumentRequestPayload"
            - type: object
              additionalProperties: true
    ArchiveDocumentRequestPayload:
      type: object
      description: Payload strutturato per richieste `CERTIFICATI_STORICI`; i campi inseriti dall'utente restano distinti in `archive_request.fields`.
      properties:
        notes:
          type: string
        archive_request:
          $ref: "#/components/schemas/ArchiveDocumentRequest"
    ArchiveDocumentRequest:
      type: object
      properties:
        query:
          type: string
          description: Testo di supporto per la ricerca archivio, generato dai campi distinti.
        submitted_by:
          enum: [web, mobile]
        applicant_user_id:
          type: string
          description: ID utente interno Folia/DB.
        applicant_fiscal_code:
          type: string
        applicant_email:
          type: string
          format: email
        fields:
          $ref: "#/components/schemas/ArchiveRequestFields"
    ArchiveRequestFields:
      type: object
      properties:
        user_id:
          type: string
          description: ID utente interno Folia/DB.
        address:
          type: object
          properties:
            street:
              type: string
            street_number:
              type: string
        protocol_number:
          type: string
        protocol_date:
          type: string
          format: date
        cadastral:
          type: object
          properties:
            sheet:
              type: string
            parcel:
              type: string
            subaltern:
              type: string
            category:
              type: string
        permit_title:
          type: string
        description:
          type: string
    ProtocolCreate:
      type: object
      required: [protocol_number, protocol_date, aoo_code]
      properties:
        protocol_number:
          type: string
        protocol_date:
          type: string
          format: date
        aoo_code:
          type: string
        register_code:
          type: string
        metadata:
          type: object
          additionalProperties: true
    AuthTicketExchange:
      type: object
      required: [ticket]
      properties:
        ticket:
          type: string
          minLength: 64
          maxLength: 64
    OperatorLogin:
      type: object
      required: [email, password]
      properties:
        tenant_code:
          type: string
        email:
          type: string
          format: email
        password:
          type: string
          format: password
        mfa_code:
          type: string
    OperatorInvitationAccept:
      type: object
      required: [token, password, password_confirmation, terms_accepted]
      properties:
        token:
          type: string
          minLength: 32
          maxLength: 160
        password:
          type: string
          format: password
          minLength: 12
        password_confirmation:
          type: string
          format: password
        terms_accepted:
          type: boolean
    OperatorInvite:
      type: object
      required: [name, email, role_codes]
      properties:
        name:
          type: string
          maxLength: 180
        email:
          type: string
          format: email
        role_codes:
          type: array
          minItems: 1
          items:
            enum: [admin_ente, istruttore, protocollo, supervisore, auditor]
    OperatorStatusUpdate:
      type: object
      required: [status]
      properties:
        status:
          enum: [active, disabled]
    Problem:
      type: object
      required: [type, title, status, detail, instance]
      properties:
        type:
          type: string
          format: uri
        title:
          type: string
        status:
          type: integer
        detail:
          type: string
        instance:
          type: string
        request_id:
          type: string
