Handling Reject Code 70: Product/Service Not Covered
Reject code 70 is the adjudication engine’s way of saying the drug itself is not payable under this plan — the member is fine, the data is fine, but the product in 407-D7 Product/Service ID either resolved to a formulary-excluded therapeutic class or is not on the plan’s drug file at all. It is the most consequential coverage reject to get right because it is a dead end for the pharmacy: unlike 75 Prior Authorization Required, there is no workflow that turns a 70 into a paid claim at the counter, so misclassifying a limit or an eligibility problem as 70 sends the pharmacist and member down a path with no exit. This guide specifies exactly when the engine emits 70, how it arises out of NDC-to-GPI resolution and formulary lookup, and the Python handler that builds the response and routes the claim, within the broader NCPDP Reject Code Reference.
The Exact Decision
The decision this handler makes is narrow: given a claim that has passed schema validation and eligibility, does the drug belong to a therapeutic class the plan covers? 70 fires in two distinct situations that share one message. The first is formulary exclusion: the 407-D7 NDC resolves cleanly through the NDC-to-GPI Crosswalk Automation to a valid GPI, but that GPI is on the plan’s exclusion list. The second is not on file: the NDC does not resolve to any GPI the formulary knows, so the plan has no coverage position on it at all. Both return 70, but they route differently downstream — an exclusion is a permanent plan decision, while a not-on-file may be a crosswalk gap worth escalating to formulary maintenance.
The hard part is not emitting 70; it is not emitting 70 when a different code is correct. The table below disambiguates 70 from the codes it is most often confused with.
| Symptom on the claim | Correct code | Why not 70 |
|---|---|---|
| GPI is excluded from the formulary | 70 Product/Service Not Covered |
This is the canonical 70 case |
| NDC resolves to no GPI (not on file) | 70 Product/Service Not Covered |
Plan has no coverage position; still 70 |
| GPI is covered but requires prior authorization | 75 Prior Authorization Required |
Drug is coverable; PA is a gate, not an exclusion |
| GPI is covered but quantity/days exceed the cap | 76 Plan Limitations Exceeded |
Drug is on formulary; only the amount is wrong |
Member is not active on 401-D1 Date of Service |
65 Patient Not Covered |
The patient, not the product, is the problem |
| Coverage ended before the fill date | 69 Filled After Coverage Terminated |
Eligibility failure precedes any formulary check |
The rule of thumb the handler encodes: 70 is a statement about the product’s coverage status, evaluated only after the member is confirmed eligible and only after the field data is confirmed readable. If any eligibility code (65, 69, 608) applies, it outranks 70; if the drug is coverable but gated, 75 or 76 applies instead.
Step-by-Step Implementation
The handler runs after eligibility and after crosswalk resolution. It takes the resolved GPI (or the fact that resolution failed), consults the versioned formulary snapshot, and produces either a pass-through or a 70 reject with an optional supplemental message.
1. Consume the resolved GPI, never re-resolve. The crosswalk already ran; the handler receives its result. A None GPI with a not-on-file status is itself a 70 trigger — do not treat it as an error to retry.
2. Look up the coverage position in the versioned formulary snapshot. The formulary is a dated, immutable snapshot so the decision is replayable during an audit. The lookup returns one of: covered, excluded, or unknown.
3. Build the 511-FB response for an exclusion or unknown. Set 501-F1 Header Response Status to R and attach 511-FB = "70". Money never appears here, but any downstream cost fields stay decimal.Decimal.
4. Optionally attach a 526-FQ additional message. For an exclusion, a short free-text hint in 526-FQ Additional Message Information helps the pharmacist (for example, a covered alternative), without ever including PHI.
5. Route the outcome. An exclusion routes to member messaging; a not-on-file routes to formulary maintenance through Fallback Routing Logic Design.
import json
import logging
import enum
from decimal import Decimal
from typing import Optional
from pydantic import BaseModel, ConfigDict
logging.basicConfig(format="%(message)s", level=logging.INFO)
logger = logging.getLogger("reject_70")
class CoveragePosition(str, enum.Enum):
COVERED = "covered"
EXCLUDED = "excluded"
UNKNOWN = "unknown" # NDC not on file -> no GPI resolved
class Reject70Input(BaseModel):
model_config = ConfigDict(frozen=True, extra="forbid")
gpi: Optional[str] # resolved GPI, or None if not on file
claim_ref: str # tokenized 402-D2, non-PHI
quantity_442e7: Decimal # 442-E7 Quantity Dispensed (Decimal)
class Reject70Result(BaseModel):
status: str # "PASS" or "REJECT"
reject_511fb: Optional[str] = None # 511-FB Reject Code
message_526fq: Optional[str] = None
route: Optional[str] = None
def lookup_coverage(gpi: Optional[str], snapshot: dict[str, str]) -> CoveragePosition:
"""Consult a versioned, immutable formulary snapshot."""
if gpi is None:
return CoveragePosition.UNKNOWN
entry = snapshot.get(gpi[:10]) # first 10 GPI digits drive coverage
if entry == "excluded":
return CoveragePosition.EXCLUDED
if entry == "covered":
return CoveragePosition.COVERED
return CoveragePosition.UNKNOWN
def handle_reject_70(inp: Reject70Input, snapshot: dict[str, str]) -> Reject70Result:
position = lookup_coverage(inp.gpi, snapshot)
if position is CoveragePosition.COVERED:
result = Reject70Result(status="PASS")
elif position is CoveragePosition.EXCLUDED:
result = Reject70Result(
status="REJECT", reject_511fb="70", # 501-F1 = R downstream
message_526fq="Excluded; see covered alternative",
route="member_messaging",
)
else: # UNKNOWN / not on file
result = Reject70Result(
status="REJECT", reject_511fb="70",
message_526fq="Product not on file",
route="formulary_maintenance",
)
# PHI GUARDRAIL: log tokenized ref + 511-FB + route only. Never 302-C2,
# 310-CA, the resolved GPI's member context, or raw claim bytes.
logger.info(json.dumps({
"event": "reject_70_eval",
"claim_ref": inp.claim_ref,
"position": position.value,
"reject_511fb": result.reject_511fb,
"route": result.route,
}))
return resultThe handler is a pure function of (resolved GPI, formulary snapshot), so it is thread-safe under the concurrent adjudication model and replayable against the exact snapshot that was live at fill time. Note that 526-FQ Additional Message Information carries only generic guidance — never a member-specific string — so the response stays PHI-free even in its human-readable field.
Figure: A not-on-file NDC and a formulary-excluded GPI both yield 70 but route differently; only a covered GPI passes through.
Verification and Testing Pattern
Test the three branches independently, and pin the disambiguation: an excluded drug is 70, but a covered-yet-gated drug must never reach this handler as a 70.
import pytest
SNAPSHOT = {"6210001000": "covered", "2710001010": "excluded"}
def _inp(gpi):
return Reject70Input(gpi=gpi, claim_ref="tok-abc",
quantity_442e7=Decimal("30"))
def test_covered_gpi_passes():
r = handle_reject_70(_inp("6210001000XXYY"), SNAPSHOT)
assert r.status == "PASS" and r.reject_511fb is None
def test_excluded_gpi_rejects_70_to_messaging():
r = handle_reject_70(_inp("2710001010XXYY"), SNAPSHOT)
assert r.reject_511fb == "70" and r.route == "member_messaging"
def test_not_on_file_rejects_70_to_maintenance():
r = handle_reject_70(_inp(None), SNAPSHOT)
assert r.reject_511fb == "70" and r.route == "formulary_maintenance"
def test_unknown_gpi_is_not_on_file():
# A GPI the snapshot has never seen is a coverage gap, still 70.
r = handle_reject_70(_inp("9999999999XXYY"), SNAPSHOT)
assert r.reject_511fb == "70"The last two tests are the load-bearing ones: they prove that a not-on-file and an unknown GPI both resolve to 70 and route to maintenance/messaging rather than silently passing.
Gotchas and PHI Guardrails
- Do not let a crosswalk gap masquerade as an exclusion. A
NoneGPI (not on file) and an explicit exclusion are both70, but the routes differ — collapsing them hides real formulary-maintenance work behind a wall of member-messaging noise. Keep theroutedistinct. 70outranks nothing above coverage. If eligibility already failed with65,69, or608, never overwrite it with70; the member’s coverage problem is the actionable one and it wins on precedence. Only evaluate70after eligibility passes.- Never confuse gated with excluded. A drug that is on formulary but needs prior authorization is
75, and one whose quantity is too high is76. Both are coverable;70means the plan will not pay for the product regardless of PA or quantity. See Handling Reject Code 75 and Handling Reject Code 76 for the neighboring handlers. - First 10 GPI digits only. Coverage is a therapeutic-class decision, so key the lookup on the first 10 GPI positions; keying on all 14 lets a manufacturer/package variation slip past an exclusion.
526-FQstays generic. Additional-message hints must never name the member or embed PHI; a covered-alternative suggestion is fine, a member-specific note is a reportable exposure.- Log the outcome, not the claim. Every log line carries the tokenized
402-D2reference, the511-FBcode, and the route — never302-C2Cardholder ID,310-CAPatient Name, or raw claim bytes.
For tier placement of the drugs that do pass this handler, the resolved GPI feeds Tier Mapping & Copay Calculation Logic; for the authoritative NDC directory that drives not-on-file escalations, see the FDA National Drug Code Directory.
Related
- NCPDP Reject Code Reference — the registry and dispatcher that place
70in precedence order against other codes. - NDC-to-GPI Crosswalk Automation — the resolution step whose not-on-file outcome is itself a
70trigger. - Tier Mapping & Copay Calculation Logic — where a covered GPI that clears
70is priced. - Handling Reject Code 75: Prior Authorization Required — the gated-but-coverable case that
70must not absorb. - Fallback Routing Logic Design — where a not-on-file
70routes for formulary maintenance.
← Back to NCPDP Reject Code Reference