Skip to main content
Beta. IR mapping is live and in active development. The surface below is real and tested; expect it to grow (more source formats, deeper composition). It is REST-only today — there is no pb.ir SDK namespace yet.
Every integration starts with the same unglamorous question: which of their types is which of ours? IR mapping makes the answer a governed artifact instead of tribal knowledge. You ingest a foreign type universe (an OpenAPI spec), then author correspondences — adjudicated, version-stamped edges between their types and your ontology’s types. The mapping tells you its own coverage, and when their spec changes, it tells you exactly which correspondences broke. It is the inverse of the Workbench: instead of authoring a model from scratch, you start from the artifact the other side already publishes.

The vocabulary

NounWhat it is
Symbol tableThe foreign type universe, resolved and version-stamped — every schema in the spec, keyed by path (#/components/schemas/Customer).
CorrespondenceThe one artifact you author: an edge from a foreign type to one of your ontology types, carrying a relation and your reasoning.
RelationFour values: identity (same thing), subset (theirs is a narrower case of ours), related (meaningfully connected, not the same), disjoint (deliberately not mapped — a recorded decision, not an omission).
CoverageThree-state completeness: mapped, disjoint, unmapped. Unmapped is work remaining; disjoint is work done.
DriftPer-correspondence flags when either side changes: a type vanished from their spec, a version hash moved on yours.

The loop

Everything runs through one endpoint: POST /v1/ir/<tool> with your API key. Errors return in the body (isError: true), never as bare HTTP failures.
1

Open a mapping session

curl -X POST https://pnbr.io/v1/ir/ir_open_mapping \
  -H "Authorization: Bearer $PENUMBRA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "project_id": "<your-project>", "name": "Commerce API → CRM" }'
The response carries a mapping_id — the session everything else operates on. Mappings persist; you can come back to one.
2

Ingest the spec

Pass a parsed OpenAPI 3.x document. The ingest walks components.schemas, resolves $ref / allOf / oneOf / anyOf, and produces the symbol table.
curl -X POST https://pnbr.io/v1/ir/ir_ingest_source \
  -H "Authorization: Bearer $PENUMBRA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "mapping_id": "<mapping-id>",
    "source_ir_id": "commerce-api@v1",
    "openapi": { ...the spec, as JSON... }
  }'
3

Adjudicate correspondences

One call per judgment. The relation and your reasoning are the artifact:
curl -X POST https://pnbr.io/v1/ir/ir_add_correspondence \
  -H "Authorization: Bearer $PENUMBRA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "mapping_id": "<mapping-id>",
    "from_path": "#/components/schemas/Customer",
    "to_type_name": "Person",
    "to_shape": "crm",
    "relation": "identity",
    "reasoning": "Same referent; their Customer is our Person in a buying role."
  }'
ir_map_property then adjudicates field-level pairs under a type-level correspondence (with roles like titular); ir_remove_correspondence and ir_unmap_property reverse decisions.
4

Analyze

curl -X POST https://pnbr.io/v1/ir/ir_analyze \
  -H "Authorization: Bearer $PENUMBRA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "mapping_id": "<mapping-id>" }'
Returns coverage, property coverage, drift, and a single healthy roll-up you can gate on.

A worked mapping

A commerce API mapped onto a CRM ontology — five judgments, five different answers:
Their typeRelationYour typeWhy
Customeridentitycrm.PersonSame referent.
Addressidentitycrm.PostalAddressSame referent.
Orderidentitycrm.SalesOrderSame referent.
LineItemrelatedcrm.OrderLineConnected, but the granularities differ.
PaymentdisjointDeliberately out of scope, on the record.
ir_analyze then reports { mapped: 4, disjoint: 1, unmapped: 3 } — the three unmapped types (CardPayment, BankPayment, OrderStatus) are the visible remaining work, not silent gaps.

Drift: when their spec changes

Re-ingest version 2 of the spec and run ir_analyze again. If Address disappeared from their schema, its correspondence flags left_dangling; surviving correspondences flag left_version_changed so you know to re-confirm them. Your integration’s semantic assumptions just became something a pipeline can check — drift CI for meaning, the way dq.audit is CI for graph writes.

The tools

ToolSafetyWhat it does
ir_open_mappingwriteOpen (or create) a persistent mapping session.
ir_ingest_sourcewriteIngest an OpenAPI 3.x document into the symbol table.
ir_add_correspondencewriteAuthor one adjudicated type-level edge.
ir_map_propertywriteAdjudicate a property pair under a correspondence.
ir_remove_correspondence / ir_unmap_propertywriteReverse a decision.
ir_review_proposalsreadGate LLM-proposed correspondences: each comes back admissible or rejected, with reasons.
ir_inspect_mappingreadRead the session: paths, correspondences, state.
ir_analyzereadCoverage, property coverage, drift, healthy.
ir_review_proposals is the agent-era piece: let a model propose the mapping, and let the deterministic gate decide what is admissible. Nothing enters the mapping unreviewed.

v1 boundaries

Honest edges of what ships today: OpenAPI 3.x is the only source format (GraphQL, protobuf, and SQL schemas are on the roadmap), composition across mapping chains is single-hop, and contradiction detection is not yet wired.