openOM

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