Verifiable report
POST /v1/summarize?mode=intelligence&report=pdf · Minimum plan: Professional (Developer during the promotion).An AI-generated summary asks you for something uncomfortable: to trust it. You don't know whether that sentence was really said in the meeting, who said it, or when. Uttera's verifiable report changes that: instead of a text you have to believe, it produces a report you can check sentence by sentence. It is our most distinctive service.
The idea is simple and deliberate: we do not decide what is true —that is for the listener to judge—. What we do is anchor each statement to the recording: we bring it out with its exact quote, the speaker, the segment and the exact minute, and we mark whether that quote is word for word in the audio. You bring the judgment; we bring where each sentence comes from, in a way that can't be argued with.
What it's for
Any situation where what was said matters and, later, someone may call it into question:
- Board and shareholder minutes — what was agreed, anchored to who said it and when.
- Expert evidence and litigation — a recording turned into proof the other side can check for themselves.
- Compliance and audit — statements and controls with their literal backing.
- Due diligence — what was claimed in the meetings, traceable sentence by sentence.
- HR procedures — interviews and hearings with a verifiable record.
- Journalism — quotes anchored to the exact minute of the source.
- Disputes with clients or suppliers — «this was said» stops being your word against theirs.
It is worth being clear, from the start, about what Uttera proves of your report —and what it doesn't—:
The evidence graph
The analysis is delivered as an evidence graph (evidence_graph): a list of
claims, the relevant statements from the recording. Each one carries
text, state (its state: stated, conditional, disputed… — the full list below),
speakers, entities, a possible depends_on (which other
claim it depends on) and an evidence array with the passages that back it
(segment_id, t_start/t_end, quote and
grounding).
Each statement is classified on two axes, so at a glance you know what it is and how much it weighs.
What it is — the type:
- Decision — something that was decided.
- Commitment — something someone committed to, with who is responsible and, if any, the deadline.
- Figure — a number: an amount, a deadline, a quantity, a percentage.
- Risk — a risk that was flagged.
- Question — a question left open.
- Contradiction — two things said that don't match.
- Claim — any other relevant statement.
How much it weighs — the state:
- Stated — said explicitly.
- Conditional — depends on a condition, which the graph flags separately.
- Inferred — deduced from the conversation; not anchored as verified.
- Disputed — those present do not agree.
- Unresolved — left open, not closed.
That grounding is the anchoring of each quote, and we say it plainly:
verified | The quote appears exactly in the transcript, at that stretch of time. |
approx | It nearly matches (same idea, close wording) within the same segment. |
unverified | It could not be anchored to a concrete stretch. It is not signed as verified (verifiable:false) and in the report it is flagged or excluded. |
🔴 Anchoring is not judging the truth. The graph says who said what and where, and that the quote is really in the audio. It does not say that what was said is true: that —and the accuracy of the transcript— is for the listener to decide. It is the same distinction as in Verifying: authenticity and anchoring are not veracity.
The figures in the report match what was said
A record of a meeting lives by its numbers: an amount, a deadline, a quantity, a percentage. So when a statement carries a figure, the report checks it against the literal quote it comes from. If the number in the report does not appear in what was said, it is flagged —not let through as if it were backed—.
Two levels of verification
Not every report needs the same rigour. So there are two levels:
✓ figures checked against what was said
✓ signed (Ed25519 + timestamp)
✓ an additional independent check that each statement is genuinely backed by its evidence
Forensic: coming soon for the higher plans.
How to request it
Through the API, it is the summary service
with ?mode=intelligence (and, to pin the report type,
report_schema/report_prompt). Add ?report=pdf for the
signed PDF: the already-validated graph goes inside the document, covered by its
signature, so the PDF is enough to check it on its own. The PDF arrives in
report.pdf_base64 with its signatures (content_signature,
container_signature, pdf_sha256, key_fingerprint). Billing
is per token, like the summary; an unverified statement is not charged separately
nor sold as verified.
curl -X POST "https://api.uttera.ai/v1/summarize?mode=intelligence&report=pdf" \
-H "Authorization: Bearer $UTTERA_API_KEY" \
-F "file=@meeting.mp3" \
| jq -r '.report.pdf_base64' | base64 -d > report.pdf
Without coding, in the Studio: tick the «Verifiable report» box, pick the output language and run; the graph is drawn on screen and you can download it (JSON) next to the PDF.
&async=1 and collect it later
(Asynchronous mode). Once the PDF is issued, anyone can
check it at Verifying a report, with no account.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.
A third-party timestamp (RFC 3161)
The two signatures say who and what. The when is added by an independent third party: at the moment of issue, each report requests an RFC 3161 timestamp from an external timestamping authority (a TSA) over the digest of its content signature. The TSA returns a token, signed by it, stating «this digest existed at this time», and that token travels inside the PDF itself, next to the signatures.
Why it matters: without it, the report's time came from our clock, inside what we ourselves signed — you would have to trust that we hadn't backdated it. With the RFC 3161 timestamp the date is attested by someone who is not us and has nothing to gain. It is what an expert witness or a court expects in order to fix when a document existed.
In the PDF footer, below the signature, the line «Trusted timestamp
(RFC 3161): … UTC · <authority>» appears. When the report is verified —on
the page or through the API— we check that the timestamp is over that content and
return the attested time in the fields timestamp_present,
timestamp_valid, timestamp_unix and tsa.
It may be absent. The timestamp is an extra, not a requirement: if the external authority does not answer at issue time, the report is still produced with its two signatures and no timestamp, and verification reports it as no external timestamp (not as invalid). Reports issued before this feature do not carry it either. And, like the Ed25519 signature, it is not a qualified electronic signature under eIDAS: it is a timestamp from a recognised provider, which is what fixes the date before a third party.
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.