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.

python
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 result

The 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.

Reject 70 coverage decision flow After eligibility passes, the handler checks whether the 407-D7 NDC resolved to a GPI. If it did not resolve, the claim rejects 70 as not on file and routes to formulary maintenance. If it resolved, the formulary snapshot is consulted: an excluded GPI rejects 70 and routes to member messaging, while a covered GPI passes through to the next rule. Eligibility passed 407-D7 resolved to a GPI? Formulary position? Reject 70 · not on file route: formulary maint. Reject 70 · excluded route: member messaging Pass → next rule covered no yes excluded covered

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.

python
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 None GPI (not on file) and an explicit exclusion are both 70, but the routes differ — collapsing them hides real formulary-maintenance work behind a wall of member-messaging noise. Keep the route distinct.
  • 70 outranks nothing above coverage. If eligibility already failed with 65, 69, or 608, never overwrite it with 70; the member’s coverage problem is the actionable one and it wins on precedence. Only evaluate 70 after 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 is 76. Both are coverable; 70 means 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-FQ stays 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-D2 reference, the 511-FB code, and the route — never 302-C2 Cardholder ID, 310-CA Patient 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.

← Back to NCPDP Reject Code Reference