openOM

Grounding AI agents in openOM

General-purpose AI extraction of an offering memorandum hallucinates - it will confidently invent an NOI, a cap rate, or a lease term that looks right and isn't. In CRE that is a liability, not a convenience. openOM removes the guess: for an openOM-enabled OM your agent reads a broker-asserted, hash-verified payload deterministically - no vision parse, no re-extraction, no hallucination.

The one thing to get right: it's an opinion, not a fact

An OM is an advertisement - the broker's opinion of value, agreed to by the seller before publication. So openOM tells your agent who asserted a figure, that it is unaltered, and as of when - never that it is true. Always carry the noiType qualifier too (in-place vs pro-forma - a very different claim). Ground the model on "the broker asserted in-place NOI = $115,625, unaltered, as of 2026-06-30", never "NOI is $115,625." Verified means provenance, not truth. Underwriting still happens at the deal desk off the broker-of-record file.

Connect the deterministic MCP server

openOM ships a deterministic MCP server, zero inference, no API key, no per-call cost. The free public grounding endpoint (serverless Cloudflare Worker) exposes the two read-side tools - om_read (read a verified payload from PDF bytes or an https URL) and om_validate. For the full six-tool surface (om_inspect, om_extract_text, om_extract_images, om_embed too), self-host:

// Public grounding endpoint (Streamable HTTP) - om_read + om_validate:
{ "mcpServers": { "openom": { "url": "https://mcp.openom.app/mcp" } } }

// Client without native Streamable-HTTP? bridge it over stdio:
{ "mcpServers": { "openom": { "command": "npx", "args": ["-y", "mcp-remote", "https://mcp.openom.app/mcp"] } } }

// Self-host the full six tools (stdio) - pip install openom-mcp:
{ "mcpServers": { "openom": { "command": "om-mcp" } } }

Rate limit: the public endpoint allows ~120 requests / 60s per client IP; over the limit it returns HTTP 429 with a Retry-After header - pace a bulk back-catalog read against it (or self-host for no limit).

Input shape: the public Worker's om_read takes flat { pdfBase64 } or { url } (exactly one); the self-hosted server takes a pdf object - { path } (stdio) or { url }/{ blobId } (hosted). Both return the same result shape.

Then the agent uses:

Which path: read vs. extract

A simple decision rule for your agent:

  1. om_inspect(pdf) → if payload.present, call om_read: deterministic, free, hash-verified. Prefer this.
  2. else the OM is not openOM-enabled → run extraction (your own model) following the extraction playbook, treat every field as an unverified guess until a human asserts it, and (optionally) om_embed it so the next read is deterministic.

Try it in one call

Point your agent at the downloadable sample OM and ask:

User: "What in-place NOI does this OM assert, and who asserted it, as of when?"
Agent → om_read({ "url": "https://openom.app/sample/openom-sample.pdf" })
Agent: "The broker (per assertedBy) asserts in-place NOI = $115,625, as of 2026-06-30 -
        unaltered since embed (hashValid: true). This is the broker's opinion, not verified truth."

Tell your agent how to treat it (system-prompt snippet)

When an openOM payload is present, use it as the broker's ASSERTED OPINION, not fact.
- Attribute every figure: "<assertedBy> asserts <field> = <value>, as of <assertedDate>".
- If verification.hashValid is not true, do NOT use the payload - it may be altered.
- Never state an OM figure as verified truth; it is an advertisement / opinion of value.
- For OMs with no openOM payload, extraction is a guess - flag it as unverified.

Why this beats re-extraction

Cold-start reality: most OMs aren't openOM-enabled yet, so your agent still needs an extractor for those - treat that output as an unverified guess, and prefer openOM-enabled OMs as the trusted path. See the extraction playbook (how to turn a raw OM into a payload with your own model), the developer quick-start, and the verify tool.