Quick-start · portal / consumer
Goal: read and trust openOM data on PDFs you host or receive - with an honest badge, never overclaiming.
Drop-in badge (one script tag)
<script src="https://openom.app/widget/openom-badge.js" defer></script>
<openom-badge src="https://cdn.example.com/deal.pdf"></openom-badge>
The badge re-fetches the bytes and runs the deterministic read/verify path. It shows Unaltered since embed for integrity, Origin-verified only when a same-domain mirror matches, and nothing when there is no payload. The badge lazy-loads (it only fetches when scrolled into view) and caches by URL, so a page with many badges is cheap.
CORS: the PDF host must send Access-Control-Allow-Origin for the
browser to read the bytes. Most listing CDNs don't - so for list/results pages, use the
precomputed path below (zero client fetch) instead.
Security: CSP + Subresource Integrity
Pin the exact widget code you execute (an immutable, content-versioned URL, so an upgrade never breaks your pinned page), and allowlist only what it needs:
<script src="https://openom.app/widget/openom-badge-1846cb5d41d0.js"
integrity="sha384-k9J35/k12QruTtB5LFhZ8EZDz3Hb2rgePvTKRwpWajz3xZudwCCGis8cMJnYbY7t" crossorigin="anonymous" defer></script>
# CSP: script-src https://openom.app ; connect-src <your-PDF-host> <your-mirror-host>
The versioned URL + its sha384 integrity are stable per release: because the
URL's bytes never change, the pin can't break; adopt a new release by bumping both together. The
badge fetches only the PDF/mirror hosts you point it at - no third-party calls. (The unversioned
/widget/openom-badge.js stays available for non-pinned use.)
Search / results pages: precompute the state (no client download)
On a grid of many listings you do not want each badge downloading a multi-MB PDF. Run the read once at ingest (server-side, no CORS) and emit the known state - the badge renders instantly with no fetch:
# at ingest, server-side (no install, no CORS): the public deterministic endpoint
curl -s https://mcp.openom.app/mcp -H 'content-type: application/json' -d '{"jsonrpc":"2.0","id":1,
"method":"tools/call","params":{"name":"om_read","arguments":{"url":"https://cdn.example.com/deal.pdf"}}}'
# store the returned state (present/absent/hash-mismatch), then on your page:
<openom-badge state="integrity-ok"></openom-badge> <!-- renders from known state, zero fetch -->
In your own code (Node)
Packages are on the way; until published, install from a clone: npm install ./js.
No install at all? Call the public om_read endpoint above (server-side, deterministic).
import { readPayloadFromBytes, summarizeDeal } from "openom-js";
const r = await readPayloadFromBytes(pdfBytes);
if (r.state === "present" && r.verification.hashValid) {
const deal = summarizeDeal(r.payload); // typed + formatted: capRate "6.25%", price w/ currency,
useIt(deal); // noiType/as-of, tenant, term, asserted-by/date
}
Provenance: a source tag (asserted/extracted)
is carried per rent period; a scalar with no source is asserted (broker-stated).
Finer per-field scalar provenance is a post-0.1 addition (#44).
Change notifications (webhooks)
Subscribe: you give a publisher (a broker/platform) a
subscription - your HTTPS
receiverUrl, a per-pair HMAC secret (exchanged out-of-band; unique per
receiver, never reused), an optional events filter, and active. In 0.1
provisioning is out-of-band (the publisher configures it however they onboard you); the schema
standardizes the shape.
Receive: verify each delivery with the
reference receiver
- signature → envelope shape → payloadHash binding, in that order. Guard
sourceUrl before fetching it (it's attacker-controlled even on a valid signature), and
dedupe by OpenOM-Event-Id (retries re-deliver the same id): ingest is at-least-once,
not idempotent. Treat the envelope's verification.* as the sender's self-report -
recompute your own.
2xx = accepted · 4xx = permanent (do not retry) · 5xx/timeout = retried with backoff