Skip to main content

Submission routing rules

Outcome

Every outbound transaction (claim, eligibility, auth, status inquiry) routes to the correct trading partner deterministically — and when the routing produces an unexpected pick, you can use the precedence simulator to see exactly which rule fired and why.

Prerequisites

ScopeWhat it lets you do
edi.routing.readView rules + run the simulator
edi.routing.writeAdd / edit / delete rules

A trading partner registered with the relevant outbound capability (2.4 — Capabilities).

What a routing rule is

A routing rule says for transactions matching X conditions, send to partner Y via companion guide Z over transport mode M. Conditions narrow the rule's scope; every condition column is optional except claim_type, tx_type, and dispatch_mode.

ColumnExample valuesNotes
tx_type837P, 837I, 270, 278, 276, 835Required.
claim_typePROFESSIONAL, INSTITUTIONAL, DENTAL, *Required; * = any.
dispatch_modeBATCH_SFTP, BATCH_REST, BATCH_AS2, BATCH_MFT, REALTIME_RESTRequired; must be one the target partner declares.
stateOH, MI, or nullOptional dimension.
payer_idA specific registered payer, or nullOptional dimension.
payer_typeMEDICAID, COMMERCIAL, MEDICARE_ADVANTAGE, or nullOptional dimension.
service_lineA specific service line, or nullOptional dimension.
priorityInteger (lower = higher precedence)Explicit precedence; default 100.
effective_from / effective_toISO dates, optionalActivation window — rule is ignored outside its window.
trading_partner_idFK to partnerRequired.
companion_guide_idFK to guideOptional.
connection_configJSONPer-rule transport overrides.

Precedence

The resolver in apps/edi-gateway/src/service/routing-resolver.ts runs in this order:

Specificity counts how many of (state, payer_id, payer_type, service_line) the rule pins. Each non-null adds 1; no other fields participate in scoring. Specificity only breaks ties between rules at the same priority.

Capability filter (dispatch_mode) is applied before scoring. A rule whose target partner has not declared the requested (dispatch_mode, tx_type) pair is dropped with reason CAPABILITY_MISSING and visible to the simulator.

Effective-date window is applied next. A rule outside its [effective_from, effective_to] window is dropped with reason OUT_OF_WINDOW.

Steps to add a routing rule

  1. Open /routing in the edi-app. The list shows every active rule grouped by transaction type.

  2. Click + New rule or click an existing rule to edit. The RoutingRuleEditPage opens.

  3. Fill the form:

    FieldNotes
    Transaction typePick from the partner's enabled capabilities only.
    Claim typePROFESSIONAL / INSTITUTIONAL / DENTAL / *.
    Dispatch modeBATCH_SFTP / BATCH_REST / BATCH_AS2 / BATCH_MFT / REALTIME_REST. Restricted to modes the chosen partner declares.
    ConditionsPick optional dimensions (state, payer, payer type, service line); leave blank for "any".
    Trading partnerRestricted to partners with the chosen (tx_type, dispatch_mode) capability.
    Companion guideRestricted to guides bound to the chosen partner + transaction.
    PriorityInteger; lower is higher precedence. Default 100.
    Effective windowOptional from/to dates.
    ActiveToggle.
  4. Use the Precedence simulator before saving. The simulator asks for the same conditions you'd see at runtime (payer, program, state, submitter NPI) and shows which rule would fire — including the rule you are currently editing.

  5. Save. The rule is live immediately for new outbound traffic.

The precedence simulator

The Precedence simulator at /routing is a backed-by-the-real-resolver debugger. Source: POST /edi/routing/resolve on edi-gateway, which delegates to routing-resolver.explainRoute().

The simulator returns:

FieldContent
selectedRuleIdThe winner (or null if no rule survived).
candidates[].decisionReasonOne of SELECTED, LOWER_PRIORITY, LOWER_SPECIFICITY, OUT_OF_WINDOW, CAPABILITY_MISSING, SCOPE_MISMATCH, INACTIVE.
candidates[].specificityCount of non-null optional dimensions on that rule.
candidates[].mismatchedDimensionsFor SCOPE_MISMATCH, the dimensions that disagreed.
candidates[].capabilityRejectionFor CAPABILITY_MISSING, the requested mode + tx-type vs. the partner's declared modes.

Common routing patterns

Single clearinghouse, all payers

rule:
tx_type: 837P
claim_type: PROFESSIONAL
dispatch_mode: BATCH_SFTP
trading_partner: change-healthcare
priority: 100

The simplest pattern — every 837P goes to a clearinghouse, which then sub-routes to the right payer.

Direct submission for one payer, clearinghouse for others

rule_a:
tx_type: 837P
claim_type: PROFESSIONAL
dispatch_mode: BATCH_SFTP
payer_id: ohio-medicaid
trading_partner: ohio-mits-direct
companion_guide: ohio-mits-2026q1
priority: 50 # lower = higher precedence

rule_b:
tx_type: 837P
claim_type: PROFESSIONAL
dispatch_mode: BATCH_SFTP
trading_partner: change-healthcare
priority: 100

Rule A wins for Ohio Medicaid via explicit lower priority. Rule B is the catch-all.

Per-state direct submission

rule:
tx_type: 837P
claim_type: PROFESSIONAL
dispatch_mode: BATCH_SFTP
state: OH
trading_partner: ohio-mits-direct
priority: 80

Wins for Ohio claims by priority; pairs with a priority: 100 catch- all. Specificity (one non-null dimension: state) only matters if another rule also has priority: 80.

Payer-type routing

rule:
tx_type: 837P
claim_type: PROFESSIONAL
dispatch_mode: BATCH_SFTP
payer_type: MEDICAID
trading_partner: medicaid-direct
priority: 70

Catches every Medicaid 837P regardless of state.

Steps to debug a misrouted transaction

A transaction that went to the wrong partner:

  1. Open the transaction at /transactions/:id. Note the partner that received it and the timestamp.

  2. Open /routing → Precedence simulator. Enter the same conditions the transaction had (transaction type, payer, program, state).

  3. The simulator shows the current winner. Compare to what the transaction actually used.

  4. If the winner has changed, look at the rule edit history. Most common: someone added a new more-specific rule or activated a tag.

  5. Fix the rule that should have fired (re-tighten conditions or deactivate the conflicting rule). New transactions route correctly immediately; the misrouted one needs replay (see 8.2 — Replay & retry).

Validation

CheckExpected
New rule visible at /routingYes.
Simulator picks the new rule when conditions matchYes.
Outbound transactions route to the new rule's partner immediatelyYes; no service restart needed.
Audit log carries routing.create rowYes.

Troubleshooting

SymptomCauseFix
Save returns "partner does not have this capability"Partner's (tx_type, dispatch_mode) map excludes this combinationEnable on the partner (2.4 — Capabilities).
Two rules tie on priority + specificity and the wrong one winsInsertion order tie-breakerAdjust priority to make precedence explicit.
Outbound stalls "no routing rule"No active rule covers the transaction's conditions, or every match was dropped for CAPABILITY_MISSING / OUT_OF_WINDOWRun the simulator with the same inputs; the rejection reason on each candidate tells you what to fix.
Simulator says rule X wins but real traffic uses YRule X was activated or had its window opened after the transaction was already in flightReplay the transaction.

Next

3.4 — Acknowledgments (999 / TA1 / 277CA)