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 tramite HTTPS. Gli URL pubblici delle guide non espongono le porte applicative interne.
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)  ·  https://pdf.cmisolutions.it

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.
Fuori tema ma qui: /xls/fix ripara i .xls che FileMaker importa saltando la prima colonna — un endpoint solo, non valeva un servizio a parte.

POST/html/pdfX-Api-Key

HTML → PDF impaginato (per stampare)

Trasforma l'HTML in un PDF pronto da stampare. Nasce da un problema concreto: da un Web Viewer di FileMaker stampare e' pressoche' impossibile, mentre un PDF dentro un contenitore si stampa, si archivia e si allega con gli strumenti di FileMaker.

Il motore e' WeasyPrint, non un browser: il testo resta testo vero e selezionabile (non un'immagine), tabelle e stili in linea sono resi bene, e ci sono formato pagina, margini, intestazione, piede e numeri di pagina. Va benissimo per l'HTML di TinyMCE e delle email.

⚠️ Di default non scarica niente dalla rete: le immagini vanno incorporate come data: URI (per esempio quelle che restituisce replykit con immagini_cid: incorpora). Con immagini_remote=1 si permettono http e https, ma solo verso indirizzi pubblici.

parametrovaloridefaultnote
htmltestoobbligatoriol'HTML da impaginare. In JSON nel campo html, oppure il corpo grezzo con Content-Type: text/html, oppure multipart nel campo file
formatoa3 | a4 | a5 | letter | legaldefault a4formato della pagina
orientamentoverticale | orizzontaledefault verticaleverso della pagina
marginimmdefault 15uno, due o quattro valori: 20, 20,15 (verticale, orizzontale), 20,15,25,15 (alto, destra, basso, sinistra)
intestazionetestofacoltativoriga ripetuta in cima a ogni pagina; {n} e {tot} come in /pdf/numera
piedetestofacoltativoriga ripetuta in fondo, es. Pag. {n} di {tot}
intestazione_possinistra | centro | destradefault centrodove va l'intestazione
piede_possinistra | centro | destradefault destradove va il piede
titolotestofacoltativotitolo nelle proprieta' del PDF, se l'HTML non ha gia' un <title>
csstestofacoltativoCSS aggiuntivo, applicato dopo quello predefinito: interruzioni di pagina, caratteri, spaziature
immagini_remote0 | 1default 0permette di scaricare immagini e fogli di stile via http/https, solo da indirizzi pubblici
nometestodefault documento.pdfnome del file nella risposta

cURL

curl -X POST \
  -H "X-Api-Key: LA_TUA_CHIAVE" \
  -H "Content-Type: application/json" \
  -d '{"html":"<h1>Preventivo 1180</h1><p>Buongiorno, ecco il dettaglio.</p>",
       "formato":"a4","margini":"20,15",
       "intestazione":"CMI Solutions",
       "piede":"Pag. {n} di {tot}",
       "titolo":"Preventivo 1180"}' \
  --output preventivo.pdf \
  "https://pdf.cmisolutions.it/html/pdf"

# oppure l'HTML grezzo, con le opzioni nella query:
curl -X POST -H "X-Api-Key: LA_TUA_CHIAVE" -H "Content-Type: text/html" \
  --data-binary @pagina.html --output stampa.pdf \
  "https://pdf.cmisolutions.it/html/pdf?formato=a4&orientamento=orizzontale"

FileMaker

# L'HTML ce l'hai gia' nel campo: non serve passare dal Web Viewer.
Set Variable [ $json ; value:
    JSONSetElement ( "{}" ;
        [ "html"         ; DOC::Corpo_HTML        ; JSONString ] ;
        [ "formato"      ; "a4"                   ; JSONString ] ;
        [ "margini"      ; "20,15"                ; JSONString ] ;
        [ "intestazione" ; DOC::Titolo            ; JSONString ] ;
        [ "piede"        ; "Pag. {n} di {tot}"    ; JSONString ] ;
        [ "nome"         ; "preventivo.pdf"       ; JSONString ]
    ) ]

Insert from URL [
    Select ; With dialog: Off ;
    Target: $pdf ;             // BINARIO: qui serve --FM-return-container-variable
    "https://pdf.cmisolutions.it/html/pdf" ;
    cURL options:
        "--data @$json "
      & "-H \"Content-Type: application/json\" "
      & "-H \"X-Api-Key: " & $$API_KEY & "\" "
      & "--FM-return-container-variable "
      & "--dump-header $intestazioni"
]
If [ PatternCount ( $intestazioni ; "X-Esito: ok" ) ]
    Set Field [ DOC::Stampa ; $pdf ]        // contenitore: si stampa e si allega da FileMaker
Else
    Show Custom Dialog [ "PDF non generato: " & $pdf ]
End If

Risposta

Il PDF binario (application/pdf), con X-Pages (numero di pagine), X-Bytes, X-Html-Bytes, X-Seconds e Content-Disposition col nome del file. Su errore un JSON {"error": "…"} con HTTP 400/413/500 e X-Esito: errore.

Da sapere

  • Il testo resta testo. Non e' una fotografia della pagina: nel PDF si seleziona, si cerca e si copia, e il file pesa pochi KB. Una pagina tipo viene resa in circa due decimi di secondo.
  • Le immagini vanno dentro l'HTML. Il modo consigliato e' il data: URI: nessuna rete, nessuna sorpresa, e funziona anche per le immagini che erano allegati della mail (replykit le restituisce gia' cosi' con immagini_cid: incorpora). Con immagini_remote=1 si scaricano quelle pubbliche, ma i redirect sono spenti e gli indirizzi non pubblici (localhost, 10.x, 172.16-31.x, 192.168.x) restano bloccati: dall'appbox porterebbero ai servizi interni.
  • Niente file://, mai. Un HTML che arriva da fuori non deve poter far leggere al server i propri file. Se una risorsa viene bloccata il PDF si genera lo stesso, semplicemente senza quella risorsa.
  • Caratteri: sul server ci sono DejaVu e la serie URW, e Arial, Helvetica e Times New Roman vengono sostituiti automaticamente con equivalenti metrici. Un carattere non installato (per esempio Calibri) ripiega su DejaVu Sans: leggibile, ma con spaziature diverse. Per un risultato prevedibile conviene indicare nel CSS una famiglia presente.
  • Interruzioni di pagina: si governano dal CSS, con page-break-before: always (o break-before: page) sugli elementi, passato nel parametro css o gia' dentro l'HTML.
  • Limiti: HTML fino a 5 MB, PDF fino a 1000 pagine (oltre, errore 400 invece di un file ingestibile) e una sola conversione per volta per chiave, come gli altri endpoint pesanti.

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" "https://pdf.cmisolutions.it/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" "https://pdf.cmisolutions.it/oauth/result?state=LO_STATE" 

Risposta

/oauth/pkce → code_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 https://pdf.cmisolutions.it/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/pdf/campiX-Api-Key

Aggiungi campi compilabili (precompilati)

Aggiunge campi fillable a un PDF che non ne ha, già precompilati. Serve a partire da un PDF "ignorante" e ottenerne uno che il cliente può confermare o integrare: i valori sono dentro ma modificabili. I campi creati sono veri campi AcroForm, quindi si rileggono e si riempiono con gli endpoint form come qualsiasi altro modulo. Tipi: testo, multiriga, checkbox, scelta (menu a tendina).

Le coordinate sono precise: l'unità nativa del PDF è il punto (1 pt = 1/72 di pollice). Puoi però indicare le misure in millimetri con unita:"mm". Dimensioni piena pagina dei formati più comuni (larghezza × altezza):

FormatoPunti (pt)Millimetri (mm)
A4595 × 842210 × 297
A5420 × 595148 × 210
A3842 × 1191297 × 420
Letter (US)612 × 792216 × 279
Legal (US)612 × 1008216 × 356

parametrovaloridefaultnote
pdfbase64—il PDF di partenza, codificato base64 (nel corpo JSON)
originealto / bassoaltosistema di coordinate: alto = la y è contata dall'alto della pagina (come sullo schermo); basso = origine PDF nativa in basso a sinistra
unitapt / mmptunità di x, y, larghezza, altezza, lato: pt = punti PDF (default); mm = millimetri. Il fontSize resta sempre in punti.
campilista di oggetti—i campi da creare. Ogni campo: tipo (testo/multiriga/checkbox/scelta), nome (univoco), pagina (da 1), x/y, larghezza/altezza (per il checkbox: lato), valore (per il checkbox true/false), opzioni (lista, solo per scelta), soloLettura (precompilato non modificabile), fontSize (0 = automatico, in punti), bordo (true/false). Misure nell'unità scelta con unita (default punti PDF).

cURL

curl -X POST -H "X-Api-Key: LA_TUA_CHIAVE" -H "Content-Type: application/json" \
  -d '{"pdf":"<BASE64>","origine":"alto","campi":[
        {"tipo":"testo","nome":"nome_cliente","pagina":1,"x":120,"y":95,"larghezza":200,"altezza":20,"valore":"Mario Rossi"},
        {"tipo":"checkbox","nome":"conferma","pagina":1,"x":160,"y":135,"lato":16,"valore":true}
      ]}' \
  "https://pdf.cmisolutions.it/pdf/campi" -o compilabile.pdf

FileMaker

Set Variable [ $body ; value: JSONSetElement ( "{}" ;
    [ "pdf" ; Base64Encode ( Documenti::PDF ) ; JSONString ] ;
    [ "origine" ; "alto" ; JSONString ] ;
    [ "campi" ; $campiJSON ; JSONArray ] ) ]
Insert from URL [ Select ; With dialog: Off ; Target: Documenti::PDF_Compilabile ;
    "https://pdf.cmisolutions.it/pdf/campi" ;
    cURL options: "-X POST -d @$body -H \"Content-Type: application/json\" -H \"X-Api-Key: " & $$API_KEY & "\"" ]

Risposta

Il PDF con i campi compilabili precompilati. Header X-Pages, X-Bytes.

Da sapere

  • I valori sono modificabili dal destinatario; usa soloLettura:true per bloccarne uno.
  • Per rileggere i valori confermati dal cliente usa gli endpoint di lettura dei campi form (i campi creati sono AcroForm standard).
  • Vuoi ragionare in mm? Aggiungi unita:"mm" e dai x/y/larghezza/altezza in millimetri (1 mm = 72/25,4 ≈ 2,83 pt). Con origine:"alto" parti dall'angolo in alto a sinistra della pagina.

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
maxwpx—ridimensiona 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 \
  "https://pdf.cmisolutions.it/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/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 "https://pdf.cmisolutions.it/pdf/delete?pages=2,4" -o ridotto.pdf

Risposta

Il PDF. Header X-Pages.

POST/pdf/encryptX-Api-Key

Proteggi con password

Cifra il PDF con una password (AES-256). La password si passa nell'header X-Pdf-Password — non 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 "https://pdf.cmisolutions.it/pdf/encrypt" -o protetto.pdf

FileMaker

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

Insert from URL [
    Select ; With dialog: Off ; Target: Documenti::PDF_Protetto ;
    "https://pdf.cmisolutions.it/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/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 \
  "https://pdf.cmisolutions.it/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 ;
    "https://pdf.cmisolutions.it/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/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 "https://pdf.cmisolutions.it/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"}' \
  "https://pdf.cmisolutions.it/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 ;
    "https://pdf.cmisolutions.it/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/from-imagesX-Api-Key

Più immagini → un PDF multipagina

Di default una immagine per pagina, nell'ordine di invio; con per_pagina=N le dispone in griglia, N per foglio. 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
per_pagina1-501quante immagini per pagina. Con 1 il comportamento è quello storico (una per foglio, che ruota per seguire l'immagine); con >1 le dispone in griglia riempiendo per righe
colonne1-50autocolonne della griglia (le righe si ricavano). Se omesso è automatico, il più quadrato possibile per l'orientamento. Solo con per_pagina>1
orientamentoverticale | orizzontaleverticaleorientamento del foglio in modalità griglia. In orizzontale la griglia automatica preferisce più colonne (es. per_pagina=2 → affiancate invece che impilate)
guttermm (0-50)6spazio fra le celle della griglia
pagea4 | letter | autoa4auto = pagina su misura dell'immagine (solo con per_pagina=1); con a4/letter l'orientamento del foglio segue l'immagine (in griglia il foglio è in verticale)
dpi36-600150risoluzione con cui viene calcolata la pagina
quality1-10085qualità JPEG delle immagini incorporate
maxwpx—rimpicciolisce le immagini più larghe prima di impaginarle
marginmm (0-50)10margine bianco esterno; 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 \
  "https://pdf.cmisolutions.it/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 ;
    "https://pdf.cmisolutions.it/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-Per-Page, X-Grid (es. 2x2, vuoto se 1/pagina), X-Bytes.

Da sapere

  • Il caso tipico: fotografi più pagine col telefono e ottieni un unico PDF. FileMaker da solo non lo fa.
  • Più immagini per pagina: ?per_pagina=4 mette 4 immagini per foglio (griglia 2×2), ?per_pagina=2 due impilate. Per decidere tu la disposizione aggiungi colonne, es. ?per_pagina=6&colonne=3 → 3 colonne × 2 righe. L'ultima pagina può restare parzialmente riempita.
  • In modalità griglia (per_pagina>1) il foglio è verticale di default, o orizzontale con orientamento=orizzontale; page=auto non è ammesso: scegli a4 o letter.
  • Con page=a4 l'immagine viene adattata al foglio (anche ingrandita, se piccola). Con page=auto la pagina prende la misura esatta dell'immagine (solo una per pagina).
  • Per un risultato più leggero, concatena con /pdf/compress?quality=60&maxw=1600, oppure passa già maxw qui.
  • Limiti anti-esaurimento risorse. Default: 50 immagini, 40 milioni di pixel per immagine, 120 milioni complessivi, 25 milioni per pagina raster e 100 MB di output. Sono configurabili con le variabili PDF_MAX_*; il servizio prepara una pagina alla volta, non l'intero lotto.

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 "https://pdf.cmisolutions.it/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.

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 \
  "https://pdf.cmisolutions.it/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 ;
    "https://pdf.cmisolutions.it/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/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 \
  "https://pdf.cmisolutions.it/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 ;
    "https://pdf.cmisolutions.it/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/numeraX-Api-Key

Numera le pagine

Stampa il numero di pagina su ogni pagina di un PDF. Il segnaposto {n} è il numero corrente, {tot} il totale; {tot} è calcolato in modo che l'ultima pagina numerata legga sempre «N di N». Le pagine ruotate a 90/270° ricevono il numero dritto e nell'angolo giusto.

parametrovaloridefaultnote
formatotesto con {n} e {tot}{n} / {tot}il testo da stampare: {n} diventa il numero di pagina, {tot} il totale; tutto il resto è scritto tale e quale. {tot} è facoltativo: per la numerazione SENZA totale usa {n} da solo. Esempi: {n} → «1», Pag. {n} di {tot} → «Pag. 1 di 8». Puoi stilizzare SOLO una parte con <u>…</u> (sottolineato) e <b>…</b> (grassetto): es. Pag. <u>{n}</u> di {tot} sottolinea solo il numero
posizionebasso/alto-destra/centro/sinistrabasso-destradove mettere il numero
dan° pagina1prima pagina da numerare: le precedenti restano intatte (salta una copertina)
inizionumero1primo numero stampato
dim4-729corpo del carattere in punti
marginemm (0-100)12distanza dal bordo
coloreesadecimale#444444colore del testo
font14 font standard oppure un font del pannelloHelveticai 14 font PDF standard (Helvetica, Times-Roman, Courier…) o il nome di un font caricato in /admin (gli stessi TTF di squish, es. anton). Ignoto → Helvetica; l'header X-Font-Used dice quale è stato applicato
urlhttp/https/mailto—se presente, il numero diventa un link cliccabile verso questo indirizzo. {n} e {tot} sono sostituiti anche qui, così ogni pagina può puntare a un URL diverso (es. https://…/pagina/{n}). Per farlo sembrare un link aggiungi colore=#0000ee

cURL

curl -X POST -H "X-Api-Key: LA_TUA_CHIAVE" \
  --data-binary @documento.pdf \
  "https://pdf.cmisolutions.it/pdf/numera?formato=Pag.%20{n}%20di%20{tot}&posizione=basso-centro" -o numerato.pdf

FileMaker

Set Variable [ $pdf ; value: Documenti::PDF ]
Insert from URL [
    Select ; With dialog: Off ; Target: Documenti::PDF_Numerato ;
    "https://pdf.cmisolutions.it/pdf/numera?da=2&formato=Pag.%20{n}%20di%20{tot}" ;
    cURL options: "--data-binary @$pdf -H \"X-Api-Key: " & $$API_KEY & "\""
]

Risposta

Il PDF numerato. Header X-Pages, X-Pages-Numbered (quante numerate), X-Font-Used (font applicato), X-Linked (1 se il numero è un link), X-Bytes.

Da sapere

  • Solo il numero, senza totale: ?formato={n} (o ?formato=Pagina%20{n}). Il {tot} si mette solo se lo vuoi.
  • Stile su una parte del testo — dentro formato due tag delimitano il tratto da stilizzare:
     ·  <u>…</u> = sottolineato
     ·  <b>…</b> = grassetto (solo font standard; su un font del pannello resta regolare)
    Come concatenarli. In fila su parti diverse: <u>Vai</u> a pag. <b>{n}</b>. Insieme sullo stesso tratto, annidandoli (l'ordine non conta): <b><u>{n}</u></b> → grassetto e sottolineato. Ogni altro < > è testo normale, non serve alcun escaping.
  • Numero cliccabile: con ?url=https://… il numero diventa un link. L'area cliccabile copre tutto il testo di formato, anche sulle pagine ruotate. Solo schemi http/https/mailto. Con da=2 la copertina resta senza numero e senza link.
  • Il numero viene fuso sopra il contenuto esistente, non lo sostituisce.
  • I font sono gli stessi del pannello /admin (quelli di squish): scrivi in font= il nome del preset (es. font=anton) oppure uno dei 14 standard (Helvetica, Times-Roman…). Il font scelto viene incorporato nel PDF, che resta autonomo. I preset sono TTF; un eventuale OTF non incorporabile ripiega su Helvetica. L'header X-Font-Used conferma quale è stato applicato.
  • Con da=2 la copertina resta senza numero e la numerazione parte dalla pagina 2 (che diventa «1 di …»).
  • Un PDF protetto si apre passando l'header X-Pdf-Password.

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 \
  "https://pdf.cmisolutions.it/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 ;
    "https://pdf.cmisolutions.it/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/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 "https://pdf.cmisolutions.it/pdf/rotate?deg=90&pages=1" -o ruotato.pdf

Risposta

Il PDF. Header X-Pages.

POST/pdf/splitX-Api-Key

Dividi in tanti PDF di una pagina

Spezza un PDF multipagina in tanti PDF da una pagina. Di default li nomina come l'originale con _numeropagina (Referto_1.pdf, Referto_2.pdf…): l'"originale" viene da ?filename= o dal nome del file multipart, altrimenti documento. Puoi anche passare una lista di nomi con ?nomi=, usati nell'ordine delle pagine.

Input accettati. 1 PDF: corpo grezzo  •  multipart file  •  JSON {"pdf_base64":"…", "nomi":["A","B"]}

parametrovaloridefaultnote
nomiNome1,Nome2,…—lista di nomi separati da virgola, applicati alle pagine nell'ordine; se sono meno delle pagine, le restanti tornano al nome di default. In JSON si usa il campo nomi (array). L'estensione .pdf è aggiunta da sé
formatjson | zipjsonjson → i file in base64 (comodo da FileMaker: cicli e salvi ognuno); zip → un unico archivio .zip con dentro i PDF già nominati
pageses. 1,3-5tuttelimita quali pagine estrarre
filenamenome—base del nome di default (<base>_<n>.pdf) quando mandi il PDF come corpo grezzo

cURL

# tanti PDF in JSON base64, nomi di default (Referto_1.pdf, Referto_2.pdf, …)
curl -X POST -H "X-Api-Key: LA_TUA_CHIAVE" --data-binary @referto.pdf \
  "https://pdf.cmisolutions.it/pdf/split?filename=Referto"

# un unico .zip con nomi tuoi
curl -X POST -H "X-Api-Key: LA_TUA_CHIAVE" --data-binary @referto.pdf \
  "https://pdf.cmisolutions.it/pdf/split?format=zip&nomi=Fronte,Retro" -o pagine.zip

FileMaker

Set Variable [ $pdf ; value: Documenti::PDF ]
Insert from URL [
    Select ; With dialog: Off ; Target: $risposta ;
    "https://pdf.cmisolutions.it/pdf/split?filename=" & Documenti::Nome ;
    cURL options: "--data-binary @$pdf -H \"X-Api-Key: " & $$API_KEY & "\""
]
# poi cicli sui file e salvi ognuno:
#   Count ( ... )  su files[i]
#   JSONGetElement ( $risposta ; "files[0].nome" )        -> Referto_1.pdf
#   Base64Decode ( JSONGetElement ( $risposta ; "files[0].pdf_base64" ) ; "Referto_1.pdf" )

Risposta

JSON: n_pagine, n_file, files[] con {pagina, nome, bytes, pdf_base64}. Con format=zip: l'archivio, header X-Files, X-Filename.

Da sapere

  • Ogni file di uscita è un PDF di una sola pagina.
  • Con nomi più corti delle pagine, le pagine in eccesso usano il nome di default; in zip, due nomi uguali non si sovrascrivono (viene aggiunto _2, _3…).
  • Per FileMaker conviene format=json (cicli su files[] e salvi ogni pdf_base64 con il suo nome); lo zip è comodo per scaricare tutto in un colpo.

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 \
  "https://pdf.cmisolutions.it/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 \
  "https://pdf.cmisolutions.it/pdf/text?mode=table&parse=1" 

FileMaker

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

Insert from URL [
    Select ; With dialog: Off ; Target: $risposta ;
    "https://pdf.cmisolutions.it/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: righe[] (ogni riga come elemento), caratteri, n_pagine, mode, normalize, dati (anagrafica utile, vedi sotto). Con parse=1 anche misure[] ([{"etichetta":"IVSd","valore":"5.8","unita":"mm","pagina":1}, …]), chiavi_duplicate (quante etichette DISTINTE si ripetono: una ripetuta 5 volte conta 1) e chiavi_duplicate_lista (l'elenco di quelle etichette). Nel PDF la chiave è l'etichetta: una misura ripetuta su più pagine è un duplicato → deduplica su etichetta. Il testo completo si ottiene con ?format=text (testo puro). Nota: le vecchie chiavi testo e pagine[] non sono più in risposta.

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à).
  • I referti a DUE COLONNE (etichetta valore unità | etichetta valore unità sulla stessa riga, es. alcuni Esaote) vengono letti per intero: con mode=table si prendono entrambe le misure di ogni riga, non solo la prima.
  • 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.
  • Chiave dati (anagrafica utile): nome_proprietario, nome_animale, nome_completo, peso, clinica, ecografo. Dal PDF è best-effort — ogni ecografo impagina l'anagrafica a modo suo: cerca le etichette note (COGNOME, NOME, PESO, ISTITUTO… e gli equivalenti inglesi) e la marca dai metadati/testo se citata. I campi non trovati restano vuoti (nessun valore inventato). Per gli XML gli stessi campi sono più affidabili: vedi /xml/misure.

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 "https://pdf.cmisolutions.it/text/misure" 

FileMaker

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

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

Risposta

JSON: n_misure, caratteri, sezioni[], chiavi_duplicate (coppie sezione+etichetta DISTINTE che si ripetono: una ripetuta 5 volte conta 1) + chiavi_duplicate_lista (l'elenco) 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/xls/fixX-Api-Key

Correggi un .xls che FileMaker importa senza la prima colonna

Non è un'operazione su PDF, ma vive qui per non aggiungere un processo sull'appbox. Certi programmi generano .xls in cui il record DIMENSIONS del foglio — l'area dati dichiarata — è scritto con indici 1-based invece che 0-based. Excel se ne infischia e ricalcola l'area dalle celle (per questo apri e risalva guarisce il file), FileMaker invece si fida e comincia dalla colonna B: la prima colonna sparisce dall'import. Qui l'area vera viene ricalcolata dalle celle presenti e quei 14 byte riscritti: stessa lunghezza, nessuna ricostruzione del file, testi, formati e date restano identici. Un file già sano torna tale e quale, quindi puoi metterlo nel flusso di import senza chiederti se serve.

Input accettati. 1 .xls: corpo grezzo  •  multipart file  •  JSON {"xls_base64":"…"}

parametrovaloridefaultnote
check1—solo diagnosi: risponde JSON e non tocca il file
formatbin | jsonbinbin → il .xls corretto; json → diagnosi + xls_base64
filenamenome—nome del file restituito (l'estensione è sempre .xls); col multipart si eredita dal file inviato

cURL

# correggi
curl -X POST -H "X-Api-Key: LA_TUA_CHIAVE" \
  --data-binary @export.xls "https://pdf.cmisolutions.it/xls/fix" -o export_ok.xls

# solo diagnosi, senza modificare nulla
curl -X POST -H "X-Api-Key: LA_TUA_CHIAVE" \
  --data-binary @export.xls "https://pdf.cmisolutions.it/xls/fix?check=1" 

FileMaker

Set Variable [ $xls ; value: Import::File ]

Insert from URL [
    Select ; With dialog: Off ; Target: Import::File_OK ;
    "https://pdf.cmisolutions.it/xls/fix" ;
    cURL options: "--data-binary @$xls -H \"X-Api-Key: " & $$API_KEY & "\""
]
# poi il file corretto va su disco e si importa come sempre:
#   Export Field Contents [ Import::File_OK ; "$temp/export_ok.xls" ]
#   Import Records [ "$temp/export_ok.xls" ]

Risposta

Il .xls (corretto o identico). Header: X-Corretto (1 = qualcosa è stato riscritto), X-Fogli, X-Fogli-Corretti, X-Filename, X-Bytes e X-Diagnosi (JSON con area dichiarata e area reale, foglio per foglio).
Con check=1 o format=json: JSON con corretto, da_correggere, n_fogli, fogli[] (dichiarato / reale / da_correggere) e, con format=json, xls_base64.

Da sapere

  • Corregge tutti i fogli della cartella, non solo il primo.
  • È idempotente: rilanciarlo su un file già corretto non fa nulla (X-Corretto: 0) e restituisce gli stessi byte. Mettilo pure sempre prima dell'import.
  • Vale solo per i .xls binari (BIFF8, firma OLE). Un .xlsx o un HTML travestito da .xls ricevono 400 con la spiegazione: quelli vanno convertiti, non corretti.
  • Se il difetto viene da un gestionale che esporta, vale la pena segnalarlo a chi lo sviluppa: è un off-by-one nel loro writer. Questo endpoint è la stampella, non la cura.

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
compatsimplexml—modalità 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 "https://pdf.cmisolutions.it/xml/json" 

FileMaker

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

Insert from URL [
    Select ; With dialog: Off ; Target: $risposta ;
    "https://pdf.cmisolutions.it/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:nodo → nodo. 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.

La chiave dati porta l'anagrafica utile del referto (nome proprietario e animale, peso, clinica, marca ecografo); i campi che la fonte non contiene restano vuoti.

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

parametrovaloridefaultnote
formatoesaote | samsung | philips— (obbligatorio)un adattatore per costruttore: gli schemi XML non si somigliano affatto (Esaote <ExamExport>, Samsung Medison <UltraSound>, Philips HL7 CDA <ClinicalDocument>). L'elenco aggiornato è su GET /xml/formati

cURL

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

FileMaker

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

Insert from URL [
    Select ; With dialog: Off ; Target: $risposta ;
    "https://pdf.cmisolutions.it/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 (chiavi DISTINTE che si ripetono: una ripetuta 5 volte conta 1) + chiavi_duplicate_lista (l'elenco), misure[] e dati (anagrafica: nome_proprietario, nome_animale, nome_completo, peso, clinica, ecografo; campi vuoti se assenti nella fonte). 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'.
  • Samsung Medison (formato=samsung, radice <UltraSound>): la sezione è la caption dell'Exam (LV (M), MV, AV, Aorta…), il valore è result (già col segno corretto: es. AV Vmax vale 1.72 anche se il campione grezzo m1 è −1.72), la chiave univoca è l'id del Label (es. CARD_IVSD_M_ITEM). Oltre a etichetta/valore/unita ci sono sezione, tipo_risultato, m1, side, location. I campi Esaote (paragrafo, capitolo, calcolata…) valgono solo per formato=esaote.
  • Philips (formato=philips, radice <ClinicalDocument>, standard HL7 CDA): le misure stanno nelle sezioni 2D, M-mode, Doppler e Altre misurazioni. Oltre a etichetta/valore/unita tornano sezione e gruppo; la chiave univoca è sezione - gruppo - etichetta. Il gruppo è indispensabile: la stessa etichetta ricorre sotto contesti diversi (Vol LV sta sotto A4Cd e A4Cs, Area/Lunghezza sotto molti gruppi). Attenzione: alcuni export Philips sono solo il 'guscio' del referto (intestazione, firma) senza misure — lì torna n_misure=0 con avviso, va riesportato includendo le misure.
  • Chiave dati — cosa aspettarsi: il nome è quasi sempre un unico campo mescolato (MARCHESE,LOLA,COCKER,7KG Philips; ROVELLI,AXEL,CAVALIER Esaote; ZANNA, PIPPO Samsung): torna grezzo in nome_completo e diviso best-effort in nome_proprietario (1º campo) / nome_animale (2º; un nome senza virgole va tutto in nome_animale). Il peso come campo a sé è raro: si recupera dal nome quando c'è (7KG). La clinica c'è su Philips (custodian) ed Esaote2 (InstitutionName), non sugli altri. Il modello ecografo non è nei file, quindi ecografo porta la marca (Philips/Esaote/Samsung). Esaote4 e simili non hanno anagrafica → solo la marca.

Comuni

Autenticazione e stato del servizio

Valgono per ogni endpoint di questo servizio.

GET/healthsenza chiave

Stato del servizio

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

cURL

curl https://pdf.cmisolutions.it/health

Risposta

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

—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.