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.
The quickest way is the page. At uttera.ai/en/verify you
upload the PDF and that is it: there is nothing to copy or paste, because the signature
travels inside the file itself. It is meant for whoever receives a report and is not going
to write code — a lawyer, an expert witness, the other party. The rest of this chapter is
for doing it yourself, with curl or without going through us.
🔴 Authenticity is not truth. Verifying a report tells you where the document comes from and that nobody has modified it. It does not say that what it says is true. The report is generated from a recording provided by the client, and both the accuracy of the transcription and the truth of what is said in it are their responsibility. Uttera did not witness what was recorded, does not check it and does not certify it.
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 is appended after the finished document, on a
line at the end of the file starting with %%UTTERA-FIRMA-V1:. A PDF reader
never sees it: the format says to read from the last startxref, so anything
after the %%EOF is ignored.
That is what makes the PDF alone enough to check both signatures. It used to be
returned separately by the API and you had to keep it next to the file: the moment somebody
forwarded the report by email, the signature was left behind and the document could not be
verified at all. The API still returns it in report.container_signature for
anyone who prefers to archive it separately.
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
With the PDF and nothing else. This is the normal mode: the signature is inside the file, so nothing you had to keep separately is needed. It checks both signatures at once:
curl -s -X POST https://api.uttera.ai/v1/reports/verify \
-H "Content-Type: application/json" \
-d "{\"modo\":\"pdf\",\"pdf_b64\":\"$(base64 -w0 report.pdf)\"}"
{"mode": "pdf", "signed": true,
"valid_container": true, "valid_content": true,
"key_fingerprint": "56ac333b…",
"fields": {"cliente": "…", "titulo": "…", "generado": "1789641036", …}}
Both true is the good case. valid_content true with
valid_container false means the text is ours but the file has been saved
again — any PDF tool that opens and writes it changes the bytes — which is not the same
as a forgery. signed false means the file carries no signature of ours, or it
has been stripped.
The two modes below are for when you hold the pieces separately: an older report, or a signature archived on its own.
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.