pdfservice

Ogni POST di lavoro richiede l'header X-Api-Key; restano aperti /health, la pagina di test e i link condivisi. Il servizio è raggiungibile da Internet senza TLS: non ci sono password o dati riservati negli URL, ma i documenti viaggiano in chiaro.
Due regole che fanno risparmiare ore, per FileMaker:
1) In ingresso il contenuto (immagine, PDF, XML) va sempre referenziato da una variabile--data-binary @$var — mai incollato dentro la stringa delle opzioni cURL. FileMaker spezza le opzioni sugli spazi, e un < subito dopo un = viene letto come "leggi da file". Un XML o un binario incollati inline non arriveranno mai interi.
2) In uscita, se il Target dell'Insert from URL è una variabile (non un campo container), va aggiunta l'opzione --FM-return-container-variable: senza, FileMaker tratta la risposta binaria come testo e dà errore 507. Con un campo container come Target non serve. Vale per tutti gli endpoint che restituiscono un file (immagini, PDF, PNG).

pdfservice

Manipolazione e compressione PDF (porta 3003)  ·  http://pdf.cmisolutions.it:3003

Estrae, unisce, inserisce, elimina e ruota pagine; comprime; crea PDF da immagini; legge e compila moduli fillable; estrae testo; protegge con password. Usa pypdf (+ Pillow, cryptography): pure-Python, niente Ghostscript, che su appbox sarebbe effimero. Le operazioni sulle pagine non rasterizzano nulla: la qualità non viene toccata.
PDF protetti: qualunque operazione accetta un PDF con password se mandi l'header X-Pdf-Password.

POST/pdf/extractX-Api-Key

Estrai pagine in un nuovo PDF

Costruisce un PDF con solo le pagine indicate, nell'ordine in cui le scrivi. Quindi serve anche a riordinare (pages=3,1,2) o a duplicare una pagina (pages=1,1,2).

parametrovaloridefaultnote
pageses. 1,3,5-8obbligatorio1-based; i range discendenti invertono (5-3 → 5,4,3); duplicati ammessi

cURL

curl -X POST -H "X-Api-Key: LA_TUA_CHIAVE" \
  --data-binary @documento.pdf \
  "http://pdf.cmisolutions.it:3003/pdf/extract?pages=1,3,5-8" -o estratto.pdf

FileMaker

Set Variable [ $pdf ; value: Documenti::PDF ]

Insert from URL [
    Select ; With dialog: Off ; Target: Documenti::PDF_Estratto ;
    "http://pdf.cmisolutions.it:3003/pdf/extract?pages=1,3,5-8" ;
    cURL options: "--data-binary @$pdf -H \"X-Api-Key: " & $$API_KEY & "\""
]

Risposta

Il PDF. Header X-Pages, X-Bytes.

Da sapere

  • Se chiedi una pagina fuori range ricevi 400 con il numero di pagine reale: usa /pdf/info per saperlo prima.

POST/pdf/mergeX-Api-Key

Unisci più PDF in uno

Concatena i PDF nell'ordine di invio. Servono almeno due file. A differenza degli altri endpoint accetta più documenti, quindi non può usare il corpo grezzo: o multipart, o JSON con i PDF in base64.

Input accettati. multipart: più campi file  •  JSON: {"pdfs": ["<base64>", "<base64>"]}

cURL

curl -X POST -H "X-Api-Key: LA_TUA_CHIAVE" \
  -F file=@primo.pdf -F file=@secondo.pdf \
  "http://pdf.cmisolutions.it:3003/pdf/merge" -o unito.pdf

FileMaker

Set Variable [ $j ; value:
    JSONSetElement ( "{}" ;
        [ "pdfs[0]" ; Base64EncodeRFC ( 4648 ; Documenti::PDF_A ) ; JSONString ] ;
        [ "pdfs[1]" ; Base64EncodeRFC ( 4648 ; Documenti::PDF_B ) ; JSONString ] ) ]

Insert from URL [
    Select ; With dialog: Off ; Target: Documenti::PDF_Unito ;
    "http://pdf.cmisolutions.it:3003/pdf/merge" ;
    cURL options: "-H \"Content-Type: application/json\" "
      & "-H \"X-Api-Key: " & $$API_KEY & "\" --data @$j"
]

Risposta

Il PDF unito. Header X-Pages.

Da sapere

  • Il base64 gonfia del ~33%: su PDF grossi il multipart è più leggero. Entrambi sono supportati, scegli tu.

POST/pdf/insertX-Api-Key

Inserisci un PDF dentro un altro

Infila tutte le pagine dell'inserto dentro il PDF base, in un punto qualunque.

Input accettati. multipart: campi base e insert  •  JSON: {"base": "<b64>", "insert": "<b64>"}

parametrovaloridefaultnote
atnumero di paginain codainserisce PRIMA di quella pagina: at=1 mette in testa, omesso mette in fondo

cURL

curl -X POST -H "X-Api-Key: LA_TUA_CHIAVE" \
  -F base=@documento.pdf -F insert=@allegato.pdf \
  "http://pdf.cmisolutions.it:3003/pdf/insert?at=3" -o risultato.pdf

FileMaker

Set Variable [ $j ; value:
    JSONSetElement ( "{}" ;
        [ "base"   ; Base64EncodeRFC ( 4648 ; Documenti::Base ) ; JSONString ] ;
        [ "insert" ; Base64EncodeRFC ( 4648 ; Documenti::Inserto ) ; JSONString ] ) ]

Insert from URL [
    Select ; With dialog: Off ; Target: Documenti::Risultato ;
    "http://pdf.cmisolutions.it:3003/pdf/insert?at=3" ;
    cURL options: "-H \"Content-Type: application/json\" "
      & "-H \"X-Api-Key: " & $$API_KEY & "\" --data @$j"
]

Risposta

Il PDF risultante. Header X-Pages.

POST/pdf/deleteX-Api-Key

Elimina pagine

Restituisce il PDF senza le pagine indicate. Non si possono eliminare tutte.

parametrovaloridefaultnote
pageses. 2,4-6obbligatoriostessa sintassi di extract

cURL

curl -X POST -H "X-Api-Key: LA_TUA_CHIAVE" \
  --data-binary @documento.pdf "http://pdf.cmisolutions.it:3003/pdf/delete?pages=2,4" -o ridotto.pdf

Risposta

Il PDF. Header X-Pages.

POST/pdf/rotateX-Api-Key

Ruota pagine

Ruota le pagine indicate, o tutte se ometti pages.

parametrovaloridefaultnote
degmultiplo di 9090in senso orario; 270 = 90 antiorario
pageses. 1,3tuttequali pagine ruotare

cURL

curl -X POST -H "X-Api-Key: LA_TUA_CHIAVE" \
  --data-binary @documento.pdf "http://pdf.cmisolutions.it:3003/pdf/rotate?deg=90&pages=1" -o ruotato.pdf

Risposta

Il PDF. Header X-Pages.

POST/pdf/from-imagesX-Api-Key

Più immagini → un PDF multipagina

Una immagine per pagina, nell'ordine di invio. Applica l'orientamento EXIF (le foto da smartphone vengono raddrizzate) e appiattisce la trasparenza su bianco, perché il PDF non ha canale alfa. Sta fra gli endpoint PDF perché l'uscita è un PDF: puoi concatenare con /pdf/compress o /pdf/merge senza cambiare servizio né chiave.

Input accettati. multipart: più campi file  •  JSON: {"images": ["<base64>", "<base64>"]}

parametrovaloridefaultnote
pagea4 | letter | autoa4auto = pagina su misura dell'immagine; con a4/letter l'orientamento del foglio segue quello dell'immagine
dpi36-600150risoluzione con cui viene calcolata la pagina
quality1-10085qualità JPEG delle immagini incorporate
maxwpxrimpicciolisce le immagini più larghe prima di impaginarle
marginmm (0-50)10margine bianco; ignorato con page=auto

cURL

curl -X POST -H "X-Api-Key: LA_TUA_CHIAVE" \
  -F file=@pagina1.jpg -F file=@pagina2.jpg -F file=@pagina3.jpg \
  "http://pdf.cmisolutions.it:3003/pdf/from-images?page=a4&dpi=150" -o documento.pdf

FileMaker

Set Variable [ $j ; value:
    JSONSetElement ( "{}" ;
        [ "images[0]" ; Base64EncodeRFC ( 4648 ; Scansioni::Foto1 ) ; JSONString ] ;
        [ "images[1]" ; Base64EncodeRFC ( 4648 ; Scansioni::Foto2 ) ; JSONString ] ) ]

Insert from URL [
    Select ; With dialog: Off ; Target: Documenti::PDF ;
    "http://pdf.cmisolutions.it:3003/pdf/from-images?page=a4" ;
    cURL options: "-H \"Content-Type: application/json\" "
      & "-H \"X-Api-Key: " & $$API_KEY & "\" --data @$j"
]

Risposta

Il PDF. Header X-Pages, X-Images, X-Bytes.

Da sapere

  • Il caso tipico: fotografi più pagine col telefono e ottieni un unico PDF. FileMaker da solo non lo fa.
  • Con page=a4 l'immagine viene adattata al foglio (anche ingrandita, se piccola). Con page=auto la pagina prende la misura esatta dell'immagine e non c'è nessun adattamento.
  • Per un risultato più leggero, concatena con /pdf/compress?quality=60&maxw=1600, oppure passa già maxw qui.

POST/pdf/compressX-Api-Key

Comprimi un PDF

Due leve indipendenti. images=1 ricomprime le immagini dentro il PDF con Pillow: è lossy ed è il guadagno vero sui documenti scansionati. lossless=1 comprime gli stream e deduplica gli oggetti: è sempre sicuro ma rende poco. Su un PDF di solo testo l'unica leva utile è la seconda, e il guadagno resta piccolo.

parametrovaloridefaultnote
quality1-10070qualità JPEG delle immagini ricompresse
maxwpxridimensiona le immagini più larghe: è la leva più potente
images0 | 11ricomprimi le immagini interne (lossy)
lossless0 | 11comprimi stream e deduplica oggetti (sicuro)

cURL

curl -X POST -H "X-Api-Key: LA_TUA_CHIAVE" \
  --data-binary @scansione.pdf \
  "http://pdf.cmisolutions.it:3003/pdf/compress?quality=60&maxw=1600" -o compresso.pdf

Risposta

Il PDF. Header: X-Original-Bytes, X-Compressed-Bytes, X-Ratio, X-Images-Found, X-Images-Recompressed, X-Images-Skipped, X-Fallback-Original.

Da sapere

  • Non restituisce mai un PDF più grande dell'originale: se non si guadagna torna l'originale intatto con X-Fallback-Original: 1.
  • Misurato su una scansione da 1,18 MB: quality=60−73%; con maxw=1000−94%; solo lossless → 0% (fallback).
  • Salta le immagini sotto gli 8 KB, quelle con trasparenza e i formati esotici (CCITT, JBIG2): li conta in X-Images-Skipped invece di fallire.
  • Non fa subsetting dei font né ottimizzazioni strutturali: per quelle servirebbe Ghostscript nell'immagine Docker.

POST/pdf/fieldsX-Api-Key

Leggi i campi di un modulo fillable

Elenca i campi di un AcroForm: nome, tipo, valore attuale e — per checkbox, radio e menu — i valori ammessi. Usalo sempre prima di compilare: senza i nomi esatti e i valori ammessi, /pdf/fill scrive nel vuoto.

cURL

curl -X POST -H "X-Api-Key: LA_TUA_CHIAVE" \
  --data-binary @modulo.pdf "http://pdf.cmisolutions.it:3003/pdf/fields" 

Risposta

JSON: campi[] con nome, tipo (testo/checkbox/radio/menu/lista/firma/pulsante), valore, opzioni, solo_lettura, obbligatorio, max_len; più totale, pagine e xfa.

Da sapere

  • Se xfa è true il modulo è un form dinamico Adobe: non è compilabile per questa via (vedi /pdf/fill).

POST/pdf/fillX-Api-Key

Compila un modulo fillable

Scrive i valori nei campi e restituisce il PDF compilato. I nomi devono corrispondere esattamente a quelli di /pdf/fields; per checkbox e menu vanno usati i valori ammessi (es. /Yes, non true).

Input accettati. JSON: {"pdf_base64": "…", "values": {"Cognome": "Puppo"}}  •  multipart: file + values (JSON come stringa)

parametrovaloridefaultnote
flatten0 | 10prova a rendere i valori definitivi (vedi note)

cURL

curl -X POST -H "X-Api-Key: LA_TUA_CHIAVE" \
  -F file=@modulo.pdf \
  -F 'values={"Cognome":"Puppo","Accetto":"/Yes"}' \
  "http://pdf.cmisolutions.it:3003/pdf/fill" -o compilato.pdf

FileMaker

Set Variable [ $j ; value:
    JSONSetElement ( "{}" ;
        [ "pdf_base64"     ; Base64EncodeRFC ( 4648 ; Documenti::Modulo ) ; JSONString ] ;
        [ "values.Cognome" ; Anagrafica::Cognome ; JSONString ] ;
        [ "values.Accetto" ; "/Yes" ; JSONString ] ) ]

Insert from URL [
    Select ; With dialog: Off ; Target: Documenti::Compilato ;
    "http://pdf.cmisolutions.it:3003/pdf/fill" ;
    cURL options: "-H \"Content-Type: application/json\" "
      & "-H \"X-Api-Key: " & $$API_KEY & "\" --data @$j"
]

Risposta

Il PDF compilato. Header: X-Fields-Filled, X-Fields-Unknown, X-Flattened, X-Flatten-Warning.

Da sapere

  • Se il risultato sembra vuoto, guarda X-Fields-Unknown: elenca i nomi che hai mandato ma che nel PDF non esistono. Se nessun nome esiste ricevi 400 con l'elenco di quelli veri.
  • flatten=1 non è affidabile: nei collaudi pypdf non ha appiattito nulla. L'endpoint quindi verifica il risultato e dichiara la verità: se i campi restano editabili trovi X-Flattened: 0 e un avviso. I valori sono comunque scritti.
  • I form XFA vengono respinti con 400: pypdf scriverebbe il guscio AcroForm ma il viewer mostra il livello XFA e i valori non si vedrebbero. Meglio un errore che un PDF finto-compilato.
  • Compilare un PDF firmato digitalmente ne invalida la firma.

POST/pdf/textX-Api-Key

Estrai il testo

Restituisce il testo del PDF, per cercarlo, indicizzarlo o popolare campi da FileMaker (es. leggere i valori da un referto). Funziona sui PDF con un vero livello di testo; su un PDF scansionato (immagini) torna vuoto, con un avviso.

parametrovaloridefaultnote
modeplain | layout | columns | table | streamplainper parse=1: table copre i referti a colonne allineate (la maggioranza), plain quelli con più misure per riga separate da spazi singoli, stream quelli con etichetta e valore su righe diverse. columns separa le colonne affiancate → una voce per riga; layout preserva l'allineamento a griglia
normalize0 | 10"Etichetta: 12mm" → "Etichetta 12 mm" (due punti via, unità staccata). Dà il meglio con mode=columns. Non influenza parse=1: le misure si estraggono sempre dal testo grezzo
parse0 | 10aggiunge misure[] già divise in {etichetta, valore, unita} — così non devi parsare il testo. Usare con mode=table
pageses. 1,3tuttelimita a certe pagine
formatjson | textjsontext = testo semplice invece del JSON

cURL

# una misura per riga (separa le colonne) — es. referti Mindray a due colonne
curl -X POST -H "X-Api-Key: LA_TUA_CHIAVE" --data-binary @referto.pdf \
  "http://pdf.cmisolutions.it:3003/pdf/text?mode=columns&format=text"

# misure GIA' STRUTTURATE in JSON (niente parsing lato FileMaker)
curl -X POST -H "X-Api-Key: LA_TUA_CHIAVE" --data-binary @referto.pdf \
  "http://pdf.cmisolutions.it:3003/pdf/text?mode=table&parse=1" 

FileMaker

Set Variable [ $pdf ; value: Referti::PDF ]

Insert from URL [
    Select ; With dialog: Off ; Target: $risposta ;
    "http://pdf.cmisolutions.it:3003/pdf/text?mode=table&parse=1" ;
    cURL options: "--data-binary @$pdf -H \"X-Api-Key: " & $$API_KEY & "\""
]
# poi, senza parsare il testo:
#   JSONGetElement ( $risposta ; "misure[0].etichetta" )   ->  IVSd
#   JSONGetElement ( $risposta ; "misure[0].valore" )      ->  5.8
#   Count ( ... )  loop su misure[i]   per popolare un portale

Risposta

JSON: testo (tutto), righe[] (ogni riga come elemento), pagine[] (con righe per pagina), caratteri, n_pagine, mode, normalize. Con parse=1 anche misure[]: [{"etichetta":"IVSd","valore":"5.8","unita":"mm","pagina":1}, …]. Con format=text, testo puro.

Da sapere

  • mode=columns risolve il caso dei referti in cui due valori stanno sulla stessa riga fisica (es. IVSd:5.8mm IVSs:8.1mm): li mette su righe separate. Ma per parse=1 usa mode=table: con columns un'etichetta e il suo valore incolonnati finiscono su righe diverse e la misura si perde.
  • parse=1 riconosce QUATTRO impaginazioni: con i due punti (IVSd:5.8mm); a tabella con le colonne separate da spazi (Diam. Ao   1.52 cm, anche con una colonna 'Metodo' in mezzo); più misure sulla stessa riga separate da spazi singoli (FVI MIT : 0.39 m V PICCO-E : -0.82 m/s); e a flusso verticale, un elemento per riga (etichetta / valore / marcatore / unità).
  • Un ecografo = una chiamata fissa. La modalità giusta dipende da come quel modello impagina, non dal contenuto: individuala una volta con un referto di prova e poi riusala sempre per quel modello. Riscontro sui referti di collaudo: mode=table → GE 105 misure, Mindray M9 40, Mindray Vetus 9 64, Siemens Acuson Juniper 32; mode=plain → Esaote 85; mode=stream → SonoScape 57.
  • Se una modalità restituisce 0 misure su un modello nuovo, provale tutte prima di concludere che il PDF non è leggibile: su alcuni referti l'estrazione 'layout' torna vuota o sbriciolata mentre quella grezza è pulita, e viceversa.
  • Un filtro scarta date e ID (l'unità deve iniziare con lettera o simbolo, non con / o -) e le righe con punteggiatura fra etichetta e numero (Cardiology - 1 / 2 Page non è una misura). Le intestazioni di sezione (LV, Misure 2D…) non finiscono fra le misure. Resta euristico: verifica sui tuoi modelli.
  • Se il PDF posiziona i glifi uno per uno, il testo esce a pezzi (2 4 .8 0) e le misure sarebbero spazzatura: in quel caso misure torna vuoto con avviso_parse, e resta il testo grezzo. Attenzione: capita con table su PDF che in plain o stream si leggono benissimo — è un limite dell'estrazione 'layout', non del PDF.
  • Il footer/intestazione a volte si frammenta con columns: sono righe non-misura, ignorabili.
  • L'OCR (testo da scansioni) richiede tesseract, di sistema → non disponibile su appbox.

POST/text/misureX-Api-Key

Misure da un referto in testo (TAB)

Referti in testo: TXT con colonne separate da TAB (etichetta ⇥ valore ⇥ unità) o CSV a virgole. Il separatore è riconosciuto da solo. Niente euristica sull'impaginazione. Stesse chiavi di /pdf/text?parse=1 più la sezione (presa dalle righe-titolo senza TAB, es. Mitrale, Aorta): serve perché misure omonime in sezioni diverse (HR in più valvole) sarebbero altrimenti indistinguibili.

Input accettati. testo grezzo nel corpo, multipart file, o JSON {"text": "..."}.

cURL

curl -X POST -H "X-Api-Key: LA_TUA_CHIAVE" \
  --data-binary @referto.txt "http://pdf.cmisolutions.it:3003/text/misure" 

FileMaker

Set Variable [ $txt ; value: Referti::TestoReferto ]

Insert from URL [
    Select ; With dialog: Off ; Target: $risposta ;
    "http://pdf.cmisolutions.it:3003/text/misure" ;
    cURL options: "--data-binary @$txt -H \"X-Api-Key: " & $$API_KEY & "\""
]

Risposta

JSON: n_misure, caratteri, sezioni[], chiavi_duplicate e misure[] con etichetta, valore, unita, sezione.

Da sapere

  • L'anagrafica e le intestazioni (righe senza TAB) non entrano nelle misure: o diventano la sezione corrente (se sono titoli senza cifre) o vengono ignorate.
  • La coppia sezione + etichetta è la chiave: su un referto GE di collaudo 123 misure, 123 chiavi distinte, zero collisioni.
  • Riconosce da solo il separatore: TAB (referti GE, VividIQ, Samsung — anche lo stile etichetta : valore unità), CSV a virgole (VINNO, con colonne Name/Valore/Unit), oppure colonne allineate a spazi (copia-incolla). Riscontro: GE 123 misure, VividIQ 93, Samsung 102, VINNO 21.
  • Il valore è reso com'è nel testo (punto decimale): la conversione a numero, e l'eventuale virgola, le fa FileMaker.

POST/pdf/relazioneX-Api-Key

Da JSON a PDF impaginato

Genera un PDF formattato a partire da un JSON di relazione: testata, sezioni numerate, testo giustificato, piè di pagina con «pag. X di Y». Utile per trasformare l'esito di un'analisi (adeguata verifica, perizia, referto discorsivo) in un documento presentabile e archiviabile.

Input accettati. JSON (Content-Type: application/json). Corpo: riepilogo, relazione_completa, motivazioni_esito, sintesi_operatore — tutti facoltativi: si stampano solo quelli presenti, in quest'ordine, numerati. Testata facoltativa: titolo, sottotitolo, riferimento, piede.

cURL

curl -X POST -H "X-Api-Key: LA_TUA_CHIAVE" \
  -H "Content-Type: application/json" --data @relazione.json \
  "http://pdf.cmisolutions.it:3003/pdf/relazione?filename=Relazione%20Rossi.pdf" -o relazione.pdf

FileMaker

Set Variable [ $j ; value: JSONSetElement ( "{}" ;
      [ "titolo"      ; "Relazione di adeguata verifica" ; JSONString ] ;
      [ "sottotitolo" ; Pratica::Soggetto ; JSONString ] ;
      [ "riferimento" ; "Prot. " & Pratica::Protocollo ; JSONString ] ;
      [ "riepilogo"   ; Pratica::Riepilogo ; JSONString ] ;
      [ "relazione_completa" ; Pratica::Relazione ; JSONString ] ) ]

Insert from URL [
    Select ; With dialog: Off ; Target: Pratica::PDF ;
    "http://pdf.cmisolutions.it:3003/pdf/relazione" ;
    cURL options: "--data @$j -H \"Content-Type: application/json\" "
      & "-H \"X-Api-Key: " & $$API_KEY & "\""
]

Risposta

Il PDF impaginato (A4), con X-Filename e X-Bytes.

Da sapere

  • Le righe che iniziano con -, o * diventano elenco puntato; le altre paragrafi giustificati. Il testo si spezza su più pagine da solo e la numerazione del piede si aggiorna.
  • I caratteri tipografici (apostrofi curvi, virgolette basse, trattini lunghi) e gli accenti sono resi correttamente. Eventuale HTML nel testo viene neutralizzato, non interpretato.
  • Il titolo di una sezione si stampa solo se il campo ha contenuto: niente intestazioni vuote. Se non c'è alcun campo utile la risposta è 400 con l'elenco dei campi attesi.
  • Con ?filename= si sceglie il nome del file restituito, come per gli altri endpoint.
  • Layout unico e sobrio, adatto a un fascicolo. Per una prima pagina a cruscotto (esito a colori, indicatori, checklist) servirebbero campi strutturati in più (esito, soggetto, importo…): oggi il JSON porta solo testo.

POST/oauth/pkceX-Api-Key

OAuth Vetinfo — aiuti al setup

Aiuti per il flusso OAuth 2 (Authorization Code + PKCE) di CAS Vetinfo (REV/FAR) da FileMaker. Solo il setup: il refresh a regime lo fa FileMaker in diretta verso auth.izs.it (HTTPS), così refresh_token e client_secret non passano mai da qui.

Input accettati. nessun corpo per /oauth/pkce.

cURL

# 1) genera i parametri PKCE
curl -X POST -H "X-Api-Key: LA_TUA_CHIAVE" "http://pdf.cmisolutions.it:3003/oauth/pkce"

# 2) (il browser, dopo il login Vetinfo, viene rimandato a /oauth/callback che cattura il code)
# 3) recupera il code catturato, dato lo state
curl -X POST -H "X-Api-Key: LA_TUA_CHIAVE" "http://pdf.cmisolutions.it:3003/oauth/result?state=LO_STATE" 

Risposta

/oauth/pkcecode_verifier (da tenere in FileMaker), code_challenge + code_challenge_method (nell'URL authorize), state. /oauth/result{pronto:true, code:"…"} quando la callback ha ricevuto il code, altrimenti {pronto:false}.

Da sapere

  • GET /oauth/callback è PUBBLICO (senza chiave): è lì che Vetinfo rimanda il browser col ?code=&state=, e il browser non ha la X-Api-Key. Salva il code per lo state e mostra una pagina di cortesia.
  • Il code è protetto da PKCE: anche se transita qui in chiaro (HTTP), è inutile senza il code_verifier (solo in FileMaker) e il client_secret. Per questo il setup può passare dall'appbox in sicurezza.
  • /oauth/result è monouso: il code viene cancellato appena letto e comunque scade dopo 10 minuti.
  • ⚠️ La redirect_uri {BASE}/oauth/callback va registrata presso Vetinfo (mail a farmacows@izs.it). Alcuni provider accettano solo https per le redirect pubbliche: verificalo prima.
  • Lo scambio code→token e il refresh NON passano da qui: li fa FileMaker in HTTPS diretto a Vetinfo. Vedi la procedura completa in vetinfo_oauth_filemaker.md.

POST/validaX-Api-Key

Valida partita IVA / codice fiscale

Validazione formale (il carattere di controllo torna) di una partita IVA o di un codice fiscale italiano. Intercetta gli errori di battitura — è il controllo che serve quasi sempre. Non verifica che esistano davvero (per quello servono VIES / Agenzia Entrate, esterni). /valida riconosce da sé il tipo; ci sono anche /valida/piva e /valida/cf se vuoi essere esplicito.

Input accettati. valore in ?v=, nel corpo grezzo, o JSON {"v": "..."}.

parametrovaloridefaultnote
vil valoreil codice da validare. In alternativa nel corpo grezzo o in JSON {"v": ...}

cURL

curl -X POST -H "X-Api-Key: LA_TUA_CHIAVE" \
  --data "RSSMRA80A01H501U" "http://pdf.cmisolutions.it:3003/valida" 

FileMaker

Insert from URL [
    Select ; With dialog: Off ; Target: $risposta ;
    "http://pdf.cmisolutions.it:3003/valida" ;
    cURL options: "--data " & Quote ( Anagrafica::PIVA )
      & " -H \"X-Api-Key: " & $$API_KEY & "\""
]
#   JSONGetElement ( $risposta ; "valido" )      ->  true / false
#   JSONGetElement ( $risposta ; "messaggio" )

Risposta

JSON: valido (true/false), tipo (piva | cf), valore (normalizzato), messaggio. Per il codice fiscale valido anche i dati codificati: sesso, anno, mese, giorno, codice_comune.

Da sapere

  • È una validazione formale: dice che il codice è ben formato, non che appartiene a un soggetto reale. Un codice inventato ma con il checksum giusto passa.
  • Gli spazi vengono tolti e il codice fiscale è reso maiuscolo prima del controllo, così un incolla con spazi non fa fallire la validazione.
  • Il codice fiscale gestisce l'omocodia (le cifre sostituite da lettere quando due persone avrebbero lo stesso codice): la data e il comune vengono comunque sciolti correttamente.
  • La verifica di esistenza (P.IVA attiva su VIES, CF associato a una persona) è un'altra cosa e richiede chiamate a servizi esterni: non è compresa qui.

POST/xml/jsonX-Api-Key

Converti un XML in JSON

Converte un XML qualunque in JSON fedele alla sua struttura — un documento causale, un tracciato, qualsiasi cosa: non serve che sia un referto o una fattura. Utile per leggerlo in FileMaker con JSONGetElement senza plugin.

Input accettati. XML nel corpo grezzo, multipart file, o JSON {"xml_base64": "..."}.

parametrovaloridefaultnote
attrprefisso@prefisso delle chiavi che vengono dagli attributi XML (id="7"@id)
textchiave#textchiave che porta il testo di un elemento che ha anche attributi o figli
compatsimplexmlmodalità compatibile col convertitore precedente: attributi raccolti in @attributes ed elementi vuoti come stringhe vuote

cURL

curl -X POST -H "X-Api-Key: LA_TUA_CHIAVE" \
  --data-binary @documento.xml "http://pdf.cmisolutions.it:3003/xml/json" 

FileMaker

Set Variable [ $xml ; value: Documenti::XML ]

Insert from URL [
    Select ; With dialog: Off ; Target: $risposta ;
    "http://pdf.cmisolutions.it:3003/xml/json" ;
    cURL options: "--data-binary @$xml -H \"X-Api-Key: " & $$API_KEY & "\""
]
#   JSONGetElement ( $risposta ; "documento.@id" )
#   JSONGetElement ( $risposta ; "documento.righe.riga[0].@qta" )

Risposta

Il JSON con un'unica chiave di primo livello (il nome del nodo radice). Attributi → chiavi con prefisso @; elementi ripetuti → array; elemento vuoto → null; una foglia di solo testo → la stringa direttamente.

Da sapere

  • Per conservare i vecchi percorsi FileMaker usa ?compat=simplexml: per esempio Bids.@attributes.Number. Senza il parametro l'output corrente non cambia.
  • Il parametro compat=simplexml è stato aggiunto per Cambi, per convertire in JSON l'XML ricevuto da BidInside mantenendo la struttura che in precedenza veniva prodotta tramite MBS. In questo modo gli script e i percorsi JSON già usati in FileMaker continuano a funzionare senza modifiche.
  • Il namespace viene tolto (si tiene il nome locale del nodo): x:nodonodo. Se due nodi hanno lo stesso nome locale in namespace diversi si sovrascrivono — raro, ma possibile.
  • Elemento singolo vs array: se un nodo compare una sola volta diventa un oggetto, se compare più volte un array. Senza uno schema è inevitabile; in FileMaker gestisci entrambi i casi o forza il conteggio con JSONListKeys.
  • Il testo dopo un tag interno (<p>a <b>x</b> c</p>, il « c ») non viene conservato: conta per la prosa con tag in mezzo, non per i documenti-dati.
  • XML con <!DOCTYPE> o <!ENTITY> è rifiutato (400): blocca gli attacchi XXE. Nessun documento dati legittimo usa un DOCTYPE.
  • Con ?attr= vuoto gli attributi perdono il prefisso e si mescolano ai figli: comodo se sai che non collidono, rischioso in generale.

POST/xml/misureX-Api-Key

Misure da un XML di ecografo

Molti ecografi esportano un XML oltre al PDF: quando c'è, è sempre la fonte migliore. Niente euristica sull'impaginazione — etichetta, valore e unità sono campi espliciti, quindi l'estrazione è deterministica. L'uscita ha le stesse chiavi di /pdf/text?parse=1, così il codice FileMaker non cambia a seconda della fonte.

Anagrafica, struttura, veterinario e proprietario non vengono MAI letti: gli adattatori non percorrono quei rami dell'XML, non è un filtro che si possa disattivare.

Input accettati. XML nel corpo grezzo, multipart file, o JSON {"xml_base64": "..."}.

parametrovaloridefaultnote
formatoesaote— (obbligatorio)un adattatore per costruttore: gli schemi XML non si somigliano affatto. L'elenco aggiornato è su GET /xml/formati

cURL

curl -X POST -H "X-Api-Key: LA_TUA_CHIAVE" \
  --data-binary @referto.xml "http://pdf.cmisolutions.it:3003/xml/misure?formato=esaote" 

FileMaker

Set Variable [ $xml ; value: Referti::XML ]

Insert from URL [
    Select ; With dialog: Off ; Target: $risposta ;
    "http://pdf.cmisolutions.it:3003/xml/misure?formato=esaote" ;
    cURL options: "--data-binary @$xml -H \"X-Api-Key: " & $$API_KEY & "\""
]
#   JSONGetElement ( $risposta ; "misure[0].chiave" )
#     ->  Ventricolo Sinistro - Ventricolo Sx - SIVs - IDS_CARDIO_IVSS

Risposta

JSON: formato, n_misure, bytes, chiavi_duplicate e misure[]. Ogni misura ha le chiavi compatibili col PDF (etichetta, valore, unita), la gerarchia per l'identificazione univoca (paragrafo, capitolo, abbreviazione, id e chiave già composta) e il contesto (pagina_xml, sigla, id_univoco, campo_db, modo, esame, calcolata).

Da sapere

  • Nessun campo è univoco da solo, nemmeno quello che si chiama UniqueID (misurato: 75 valori distinti su 92 misure; id 73; Abbreviation 74). Solo la combinazione paragrafo - capitolo - abbreviazione - id lo è — ed è quella che trovi già pronta in chiave. Il motivo: la stessa grandezza viene riusata da più calcoli (il diametro LVOT compare 5 volte, identico) e a volte lo stesso id vale su vasi diversi (IDS_CARDIO_PV è −1.54 m/s in Aorta e −0.80 m/s in A Polmonare: lì il contesto È il significato).
  • Vengono restituite solo le misure con isValid="true", cioè quelle davvero eseguite: nel referto di collaudo 92 valide su 679 slot previsti dal modello. Dal PDF questa distinzione non è ricavabile (uno 0.00 non si distingue da un 'non misurato').
  • calcolata distingue il valore misurato dall'operatore da quello derivato dall'ecografo con una formula (nel collaudo: 41 misurate, 51 calcolate). I derivati non aggiungono informazione e dipendono dalla formula del costruttore: per confrontare apparecchi diversi conviene basarsi sui misurati.
  • XML con <!DOCTYPE> o <!ENTITY> vengono rifiutati (HTTP 400). Senza DTD non esistono entità personalizzate, quindi cadono in blocco gli attacchi XXE (leggere /etc/passwd o raggiungere host interni) e le bombe a espansione ricorsiva. Nessun referto legittimo usa un DOCTYPE.
  • Se l'XML non è del formato dichiarato torna 200 con misure vuoto e un avviso, non un errore: così distingui 'formato sbagliato' da 'esame senza misure'.

POST/pdf/encryptX-Api-Key

Proteggi con password

Cifra il PDF con una password (AES-256). La password si passa nell'header X-Pdf-Passwordnon in query, così non finisce in log o cronologie. Owner password opzionale in X-Pdf-Owner-Password.

Input accettati. PDF nel corpo grezzo. Password nell'header X-Pdf-Password.

cURL

curl -X POST -H "X-Api-Key: LA_TUA_CHIAVE" \
  -H "X-Pdf-Password: la-mia-password" \
  --data-binary @documento.pdf "http://pdf.cmisolutions.it:3003/pdf/encrypt" -o protetto.pdf

FileMaker

Set Variable [ $pdf ; value: Documenti::PDF ]

Insert from URL [
    Select ; With dialog: Off ; Target: Documenti::PDF_Protetto ;
    "http://pdf.cmisolutions.it:3003/pdf/encrypt" ;
    cURL options: "--data-binary @$pdf "
      & "-H \"X-Api-Key: " & $$API_KEY & "\" "
      & "-H \"X-Pdf-Password: " & Documenti::Password & "\""
]

Risposta

Il PDF cifrato.

Da sapere

  • Su appbox il traffico è in chiaro (no TLS): la password nell'header è protetta dai log, ma non dalla rete. Per dati molto sensibili, cifra a valle su rete fidata.
  • Il complemento è POST /pdf/decrypt: rimuove la password (quella attuale in X-Pdf-Password) e restituisce il PDF in chiaro.

POST/pdf/infoX-Api-Key

Informazioni su un PDF

Numero di pagine, dimensioni di ogni pagina in punti, metadata, se è cifrato — e l'analisi delle immagini incorporate, che dice se conviene comprimere prima di provarci.

cURL

curl -X POST -H "X-Api-Key: LA_TUA_CHIAVE" \
  --data-binary @documento.pdf "http://pdf.cmisolutions.it:3003/pdf/info" 

Risposta

JSON: pages, sizes, metadata, encrypted, contenuto_leggibile, bytes, più:
immagini: n, oltre_8kb, bytes, quota_pct, larghezza_max_px, con_trasparenza
comprimere: conviene (si | forse | no | sconosciuto), perche, guadagno_massimo_pct.

Da sapere

  • Comodo prima di /pdf/extract per sapere quante pagine ci sono.
  • Verifica preventiva della compressione. quota_pct è la quota di byte occupata dalle immagini, ed è il tetto massimo del guadagno: se le immagini sono il 3% del file, comprimerle non può farti risparmiare di più. oltre_8kb è la condizione necessaria: /pdf/compress salta apposta le immagini sotto gli 8 KB (ricomprimere un'icona la peggiora), quindi se è 0 il guadagno è nullo anche con quota alta. Se larghezza_max_px supera ~1500, aggiungere maxw raddoppia quasi il risparmio.
  • Riscontro misurato: PDF di solo testo → 0% (e /pdf/compress restituisce l'originale); testo con logo (quota 2,6%) → 1,2%; quota 26% ma nessuna immagine sopra 8 KB → 2,0%; quota 55,6% → 28% (42% con maxw); scansione 300 dpi (quota 99,9%) → 75,9%, e 93,0% con maxw=1000.
  • Su un PDF protetto, se mandi l'header X-Pdf-Password l'analisi è completa; senza, risponde comunque 200 con contenuto_leggibile: false e conviene: "sconosciuto" — sapere che un file è cifrato è già un'informazione utile.

Comuni

Autenticazione e stato del servizio

Valgono per ogni endpoint di questo servizio.

Autenticazionesenza chiave

Come funzionano le chiavi

Una chiave = un soggetto (un database FileMaker, uno script, n8n). Ogni chiave porta l'elenco dei servizi su cui è abilitata, quindi la stessa chiave può valere su più servizi — ed è quello che vuoi: così le statistiche attribuiscono tutto il consumo a un'unica riga. Non condividere una chiave fra due soggetti: perderesti proprio l'informazione per cui l'hai creata. Le chiavi le gestisce l'amministratore: se te ne serve una, o la tua non funziona più, chiedila a lui.

cURL

# su ogni POST di lavoro
-H "X-Api-Key: cmi_xxxxxxxxxxxx"

# in FileMaker, tenendo la chiave in una variabile globale sola:
"-H \"X-Api-Key: " & $$API_KEY & "\"" 

Risposta

Se la chiave manca o è errata: 401 con un messaggio esplicito (mancante, non riconosciuta, revocata, non abilitata per questo servizio).

Da sapere

  • Restano aperti senza chiave: /health e la pagina di test su /.
  • Revoche e nuove chiavi hanno effetto immediato, senza riavviare i servizi.
  • Oltre ai servizi, una chiave può essere ristretta ai singoli endpoint. Se è abilitata al servizio ma non a quella rotta la risposta è 403 (non 401: la chiave è valida, manca il permesso). Le restrizioni si impostano dal pannello di amministrazione e valgono subito.
  • Ogni risposta porta X-Esito: ok|errore e X-Status. Servono da FileMaker: Insert from URL NON fallisce sugli errori HTTP — Get(LastError) resta 0 e il corpo dell'errore finisce nel contenitore, sovrascrivendo l'allegato buono. Con --dump-header $h si controlla l'esito prima di scrivere sul campo. Regola d'oro: mandare il risultato in una variabile, verificare, e solo allora fare Set Field.
  • Nome del file restituito: gli endpoint che restituiscono un file accettano ?filename=. Senza, si usa il nome originale se l'invio è multipart (che lo porta con sé); con --data-binary il nome si perde, quindi lì il parametro serve. L'estensione è sempre quella d'uscita, sostituita e non appesa. Il nome è anche nell'header X-Filename.

GET/healthsenza chiave

Stato del servizio

Sonda di vita, aperta e non contabilizzata: un check ogni minuto non sporca le statistiche.

cURL

curl http://pdf.cmisolutions.it:3003/health

Risposta

JSON con status, service, la versione della libreria e auth (se l'obbligo di chiave è attivo).