Verifying a report
Every PDF report we issue is cryptographically signed. Anyone —you, your client, the other side of a negotiation— can check that a report came from us and that nobody has touched it on the way, without asking us for permission and without an account.
What it is and what it is not. It is a cryptographic authenticity signature using Ed25519. It is not a qualified electronic signature in the sense of the eIDAS regulation: it proves the document came out of our system and has not changed, it is not the equivalent of a certificate from a qualified provider. We say so here, and the report says so too.
Why there are two signatures
Every report carries two, and each answers a different question:
| Signature | Over what | What it answers |
|---|---|---|
| content | what the report says: summary, transcript, client name, date and usage | "did this text come from Uttera?" — it still holds if the PDF has been reprinted, cropped or saved again |
| container | the bytes of the PDF file | "is this file exactly the one that was issued?" — it detects a single changed byte |
The content one is printed inside the report, in the footer of every page, as a short fingerprint. The container one cannot go inside —signing something that is then modified is impossible— so it travels separately: the API returns it alongside the PDF. If you are going to need it, keep it next to the file.
The public key
We publish it in three places, and all three give the same thing:
https://uttera.ai/.well-known/uttera-firma.pub the PEM, on its own
https://uttera.ai/.well-known/uttera-firma.json every key, with its dates
https://api.uttera.ai/v1/reports/public-key the one signing right now
The chain of trust is the same one DKIM or JWKS key sets use: the TLS certificate of
uttera.ai attests that the domain is ours, and that domain publishes the key.
You do not have to take our word for it over the phone.
Checking it from the command line
The endpoint is public and takes no API key:
POST https://api.uttera.ai/v1/reports/verify
Check the file (container signature). You need the PDF and the signature that
came with it in report.container_signature:
curl -s -X POST https://api.uttera.ai/v1/reports/verify \
-H "Content-Type: application/json" \
-d "{\"modo\":\"contenedor\",
\"pdf_b64\":\"$(base64 -w0 report.pdf)\",
\"firma_b64\":\"THE_CONTAINER_SIGNATURE\"}"
{"valid": true, "mode": "contenedor", "key_fingerprint": "56ac333b…"}
Check the content. You need the analysis response exactly as we returned it, plus
report.content_signature and the date from
report.generated_unix — the unix stamp, which is the value that was signed; generated_at is the written date, for reading. The stamp is printed on the report itself, next to the number:
curl -s -X POST https://api.uttera.ai/v1/reports/verify \
-H "Content-Type: application/json" \
-d '{"modo":"contenido",
"datos": { …the /v1/summarize response… },
"cliente":"Meridiano Asesores, S.L.",
"titulo":"Minutes of the meeting",
"generado":1789639533,
"firma_b64":"THE_CONTENT_SIGNATURE"}'
We store nothing for this. Verifying is a public-key operation over what you send us: we look nothing up. That is why it works on a report from years ago, and why we cannot tell you how many reports we have issued or find one you have lost.
Key rotation
Each key is used for twelve months, and is replaced sooner on any suspicion. Retired keys stay published forever: a report signed today has to be verifiable ten years from now.
That is why every report carries, alongside its signature, the fingerprint of the key that signed it. The verifier knows which one to use without trying them all.
If we ever had to retire a key because it was compromised, we would say so here with the date. And we would say the uncomfortable part too: reports signed with it stop proving anything, because whoever held the key could have signed backdated documents.