Skip to content

AI Watermark

Every AI-generated response is stamped with a latent HMAC-SHA256 watermark recorded in a log, so later you can answer "did this deployment write this text?" — and detect whether it was edited since. One endpoint does the checking, in three modes depending on what you still have: the text, the identifiers, or both.


Endpoints

POST /v1/glad/watermark/verify

What it does. Answers "did this deployment generate this text?" One endpoint, three modes, chosen automatically by what you send:

You send Mode What it proves
response_text (or text) + call_id + watermark_id live The watermark matches this exact text. Detects tampering.
call_id + watermark_id, no text lookup The watermark is registered for that call. Does not check text integrity — suitable for a third-party auditor who has the identifiers but not the content.
response_text (or text) alone search Scans the watermark log for an entry matching the text. This is the paste-a-snippet path.

Sending none of those returns 400.


# live — full integrity check
curl -s -X POST http://localhost:8080/v1/glad/watermark/verify \
  -H "Content-Type: application/json" \
  -d '{
    "call_id":       "call_abc123",
    "watermark_id":  "a8b3c1d4e5f6…",
    "response_text": "The capital of France is Paris."
  }' | jq

# search — just paste the text
curl -s -X POST http://localhost:8080/v1/glad/watermark/verify \
  -H "Content-Type: application/json" \
  -d '{"text": "The capital of France is Paris."}' | jq '{valid, call_id, match_mode}'
import httpx

def verify(text: str, call_id: str | None = None, watermark_id: str | None = None):
    body = {"response_text": text}
    if call_id and watermark_id:
        body |= {"call_id": call_id, "watermark_id": watermark_id}
    r = httpx.post("http://localhost:8080/v1/glad/watermark/verify", json=body, timeout=60)
    r.raise_for_status()                      # 400 when the body identifies nothing
    return r.json()

res = verify("The capital of France is Paris.", "call_abc123", "a8b3c1d4e5f6…")
print(res["mode"], res["valid"])
print(res["message"])
async function verify(text: string, callId?: string, watermarkId?: string) {
  const body: Record<string, string> = { response_text: text }
  if (callId && watermarkId) { body.call_id = callId; body.watermark_id = watermarkId }
  const r = await fetch("http://localhost:8080/v1/glad/watermark/verify", {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify(body),
  })
  if (!r.ok) throw new Error(await r.text())
  return r.json()
}

const res = await verify("The capital of France is Paris.", "call_abc123", "a8b3c1d4e5f6…")
console.log(res.mode, res.valid, res.message)

What comes back

{
  "verification_id": "VRF-3F9A1C7B2E5D8046A1B2",
  "valid": true,
  "call_id": "call_abc123",
  "watermark_id": "a8b3c1d4e5f6…",
  "method": "hmac_sha256_v1",
  "mode": "live",
  "timestamp": "2026-06-10T10:23:45Z",
  "verified_at": "2026-06-18T09:00:00+00:00",
  "applicable_laws": ["EU_AI_ACT", "CA_SB_942"],
  "lang": "en",
  "message": "Verification successful. The watermark_id matches the response text. …",
  "provider": "Geodesia S.R.L.",
  "product_version": "…"
}
Field Description
valid Whether the check passed.
mode Which of the three paths ran: live, lookup or the search path.
verification_id A VRF-… id for this verification act, so the check itself can be cited.
timestamp When the watermarked call was generated (null if the log entry is gone).
verified_at When this verification ran.
applicable_laws The frameworks the original call was recorded under.
message A human-readable verdict, suitable for showing to a non-technical auditor.
provider / product_version Who issued the watermark and with which build.

Request fields

Field Type Description
response_text string The text to verify.
text string Alias for response_text.
call_id string The call the text came from.
watermark_id string The HMAC recorded for that call.
session_id string Improves the live check when the original call belonged to a session.

Search mode survives copy-paste

A strict HMAC is computed over the exact bytes, so text copied out of a rendered chat — with end-of-turn markers and thinking blocks stripped — will not match. Search mode tries the exact HMAC first and then falls back to a normalised comparison against the stored response, which is what makes "paste the paragraph you received" actually work.

A failed verification is not proof of forgery

valid: false means this deployment's log does not vouch for this text as given. The text may have been edited, or the identifiers may be wrong, or it may have come from a different deployment. Read message before drawing a conclusion.


What Is an AI Watermark?

An AI watermark is a signal embedded in the text output of a language model that allows the origin of the text to be verified. Unlike visible watermarks, Geodesia's watermark is latent — it is not visible in the text but is recoverable through the verification endpoint.

This satisfies:

  • EU AI Act Article 50 — AI-generated content must be marked in a way that is detectable
  • California SB 942 — AI-generated content must carry detectable disclosure
  • Italy 132/2025 — AI content marking requirements

The watermark is a cryptographic HMAC computed over the generation parameters and embedded as metadata attached to the response. The manifest watermark (explicit disclosure in the geodesia.watermark response field) is delivered alongside the latent one.


How It Works

When Geodesia generates a response, it:

  1. Computes HMAC-SHA256(watermark_key, session_id + call_id + timestamp + model_id)
  2. Attaches the result as the watermark_token in the response metadata
  3. Records the watermark in the audit chain

To verify, you provide the watermark_token and the relevant metadata. The verifier recomputes the HMAC and checks if the provided token matches.

The watermark_key is derived from your deployment's license token. It never leaves the server.


Response Disclosure

Every Geodesia G-1 response includes a manifest watermark in the geodesia.watermark field:

{
  "geodesia": {
    "watermark": {
      "token": "hmac:v1:a8b3c1d4e5f6...",
      "generated_by": "Geodesia G-1",
      "call_id": "call_abc123",
      "timestamp": "2026-06-10T10:23:45Z",
      "disclosure": "This content was generated by an AI system."
    }
  }
}
Field Description
token The HMAC token used for verification
generated_by System identifier
call_id The call ID — can be cross-referenced with the audit chain
timestamp When the response was generated
disclosure Plain-language AI disclosure string

Watermark Configuration

Configure watermarking in config.yaml:

watermark:
  enabled: true
  algorithm: "hmac_sha256"
  include_in_response: true       # Include watermark in all API responses
  disclosure_text: "This content was generated by an AI system."
  custom_disclosure: null         # Override with jurisdiction-specific text

Regulatory Coverage

Law Requirement Coverage
EU AI Act Art. 50(1) Disclose that content is AI-generated Manifest disclosure in every response
EU AI Act Art. 50(2) Detectable machine-readable marker Latent HMAC-SHA256 token
CA SB 942 §22757(a) Watermark or tag AI-generated content Latent + manifest watermark
Italy 132/2025 Art. 4 Mark AI-generated content Disclosure field + latent token
UK DUAA 2025 Transparency of AI content Manifest disclosure