> ## Documentation Index
> Fetch the complete documentation index at: https://docs.superdial.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Input Validation

> The per-field rules applied to inputs when you create a request, and the errors they produce.

When you submit `POST /v1/requests`, every value in `inputs` is validated **synchronously, before the request is created**. If anything fails, the request is rejected with HTTP `400` and the uniform `INVALID_INPUTS` envelope. Nothing is queued and no call is placed. Fix the inputs and resubmit.

This page documents exactly which rules run. For the request body shape and the broader error taxonomy, see [Creating a Request](/guides/creating-a-request); to discover which keys a schema needs, see [Schemas](/guides/schemas#look-up-required-and-optional-inputs).

## The error shape

All input failures collapse into a single response:

```json theme={null}
{
  "error": "INVALID_INPUTS",
  "message": "Required inputs are missing or invalid.",
  "details": {
    "missingInputs": ["memberId", "providerNpi"],
    "invalidInputs": { "dateOfService": "dateOfService is invalid" }
  }
}
```

Validation produces two independent buckets, and a single request can populate both:

| Bucket                  | Type                     | Meaning                                                                               |
| ----------------------- | ------------------------ | ------------------------------------------------------------------------------------- |
| `details.missingInputs` | array of field names     | A **required** input was absent, `null`, or empty/whitespace-only.                    |
| `details.invalidInputs` | object, `field → reason` | A **supplied** value failed a format rule. The value maps to a human-readable reason. |

<Note>
  A field only lands in `missingInputs` if it's required for your schema and you didn't supply a usable value. A field lands in `invalidInputs` only when you *did* supply a non-empty value that broke a rule. Optional fields you leave out are never flagged; optional fields you *do* send are format-checked just like required ones.
</Note>

## Required vs. empty

A required input is considered **missing** when its value is `null`, absent, or a string that is empty or only whitespace after trimming. Which keys are required is schema-specific. Fetch them from [`GET /v1/schemas/{schemaId}/required-inputs`](/guides/schemas#look-up-required-and-optional-inputs). Two account features (below) can add more required keys at request time.

## Universal value rules

These two checks run on **every** non-empty input value, regardless of the field's type:

| Rule                   | Rejected when                                                                                                  | Reason returned                                      |
| ---------------------- | -------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------- |
| No scientific notation | Value matches `^-?\d+\.\d+[eE][-+]?\d+$` (e.g. `1.23e-05`), usually a spreadsheet mangling a long ID or amount | `{field} is scientific notation`                     |
| No curly braces        | Value contains `{` or `}`, usually an unrendered template placeholder like `{memberId}`                        | `{field} contains invalid characters (curly braces)` |

<Warning>
  Scientific-notation failures almost always come from Excel/CSV exports that auto-convert long numeric member IDs or claim amounts. Format the offending column as text before exporting.
</Warning>

## Per-field type rules

Each input key has a type configured by SuperDial. When a value is supplied, it's checked against the rule for its type. Fields whose key contains `phonenumber` (case-insensitive, e.g. `phoneNumber`) get the phone check on top of their type.

| Field type                                   | Rule                                                                                                                                                                                       | Reason returned                            |
| -------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------ |
| **Phone** (any key containing `phonenumber`) | Must be a valid U.S. phone number. The value is first sanitized: spaces, `()`, `-`, `+`, and a leading `1` are stripped, and the first number is taken if several are comma/`x`-separated. | `{field} is not a valid U.S. phone number` |
| **Date** (e.g. `dateOfService`)              | Must be an unambiguously parseable calendar date.                                                                                                                                          | `{field} is invalid`                       |
| **Dollar amount**                            | Must match `^-?\d+(?:\.\d{1,2})?$`: an integer or up to two decimal places, optional leading minus. No `$`, commas, or thousands separators.                                               | `{field} is invalid`                       |
| **Boolean**                                  | Must be one of `true`, `false`, `1`, `0`, `yes`, `no`, `y`, `n` (case-insensitive), a JSON boolean, or the integers `0`/`1`.                                                               | `{field} is invalid`                       |
| **String**                                   | Always valid as a plain string, unless the field carries a custom pattern (see below).                                                                                                     | `{field} is invalid: {custom message}`     |

<Note>
  **Dates:** any value the parser can read unambiguously is accepted, but to be safe and explicit, send ISO `YYYY-MM-DD`. Ambiguous values (e.g. `01/02/03`) may resolve to a date you didn't intend.
</Note>

Some string fields carry a **custom regex** and a tailored error message configured by SuperDial. When present, the value must match the pattern, and the failure reason is the field's custom message rather than the generic `{field} is invalid`. The rules currently configured:

| Field(s)                                                                        | Must be                                                     | Pattern                                                | Reason returned                                                                |
| ------------------------------------------------------------------------------- | ----------------------------------------------------------- | ------------------------------------------------------ | ------------------------------------------------------------------------------ |
| `ptan`                                                                          | Exactly 6 or 9 alphanumeric characters                      | <code>^(?:\[A-Za-z0-9]\{6}\|\[A-Za-z0-9]\{9})\$</code> | `PTAN must be 6 or 9 alphanumeric characters.`                                 |
| `billingProviderNpi`, `facilityNpi`, `renderingProviderNpi`, `practiceNPI`      | Exactly 10 digits                                           | <code>^\d\{10}\$</code>                                | `Must contain exactly 10 digits.`                                              |
| `billingProviderTaxId`, `groupTaxId`, `practiceTaxId`, `renderingProviderTaxId` | 9 digits, hyphens allowed                                   | <code>^(?:\d-?)\{9}\$</code>                           | `Must contain exactly 9 digits and may include hyphens.`                       |
| `memberFirstName`, `memberLastName`, `patientFirstName`, `patientLastName`      | Only letters, spaces, hyphens, commas, periods, apostrophes | <code>^\[A-Za-z ,.'-]+\$</code>                        | `Only letters, spaces, hyphens, commas, periods, and apostrophes are allowed.` |
| `memberId`                                                                      | Alphanumeric, spaces, and hyphens only                      | <code>^\[a-zA-Z0-9 -]+\$</code>                        | `memberId is invalid`                                                          |

<Note>
  A few fields (e.g. `billingProviderNpi`, `memberId`) don't carry a custom message, so their failures fall back to the generic `{field} is invalid`. The `memberId` character rule above always runs on a supplied value; the richer, payer-aware **Member ID validation** below is a separate, feature-gated layer.
</Note>

## Member ID validation

<Note>
  **Off by default.** These `memberId` rules run only when your account has `enforceMemberIdValidation` enabled. Ask your account team. With the feature off, `memberId` is treated as an ordinary string and only the universal rules apply.
</Note>

When enabled, `memberId` is checked against one of two rule sets, chosen by the `payerName` you send. Failures surface as `invalidInputs.memberId`. The validator picks the **BCBS** rules when `payerName` (spaces removed, lowercased) contains `bcbs`, `bluecross`, or `blueshield`; otherwise it applies the **generic** rules. Only the first failing rule is reported.

### BCBS payers

| Rule                        | Requirement                                                                                                                                                                                                                                           | Example rejection                                                                                                                                                       |
| --------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Alphanumeric only           | `^[A-Z0-9]+$`: no spaces, hyphens, or punctuation                                                                                                                                                                                                     | `memberId 'XYZ-123' contains invalid characters. BCBS member IDs must be alphanumeric only…`                                                                            |
| No test/placeholder markers | Must not contain `NULL`, `TEST`, or `ABCD`                                                                                                                                                                                                            | `memberId 'TESTID123' contains a known test/placeholder pattern ('TEST')…`                                                                                              |
| Minimum length              | At least **9** characters (Federal IDs are R + 8 digits; commercial are typically 11+)                                                                                                                                                                | `memberId 'ABC123' is too short to be a BCBS member ID (got 6 characters, minimum is 9)…`                                                                               |
| Federal-ID shape            | A 9-character ID starting with `R` must be `R` + 8 **digits**                                                                                                                                                                                         | `memberId 'R12345ABC' has the BCBS Federal length… but contains non-digit characters after the R.`                                                                      |
| Commercial alpha prefix     | A non-Federal, non-Medicare ID must begin with a 3-character BlueCard prefix: `^[A-Z][A-Z2-9][A-Z]` (letter, letter-or-digit 2–9, letter). **Additionally account-gated** (enabled only for specific accounts on top of `enforceMemberIdValidation`). | `memberId 'X1CF…' does not start with a valid BCBS plan prefix… must begin with a 3-character alpha prefix (letter, letter-or-digit 2-9, letter), e.g. 'TBG' or 'XCF'.` |

### Non-BCBS payers

| Rule                        | Requirement                                                   | Example rejection                                                                     |
| --------------------------- | ------------------------------------------------------------- | ------------------------------------------------------------------------------------- |
| Not a placeholder           | Must not be exactly `NA`, `N/A`, or `NONE` (case-insensitive) | `memberId 'N/A' is a known placeholder value. Submit the patient's actual member ID.` |
| Minimum length              | At least **4** characters                                     | `memberId 'X1' is too short (got 2 characters, minimum is 4)…`                        |
| No test/placeholder markers | Must not contain `NULL`, `TEST`, or `ABCD`                    | `memberId 'NULL' contains a known test/placeholder pattern ('NULL')…`                 |

## Per-payer required inputs

<Note>
  **Off by default.** This runs only when your account has `enforcePayerRequiredInputs` enabled, and it ramps independently of member-ID validation.
</Note>

When enabled and the payer is matched (see [Payer Phone Number Lookup](/guides/payer-resolution)), the matched payer can require additional input keys beyond your schema's base set for that request type. Any such keys you don't supply are added to `details.missingInputs`, exactly like schema-required fields. The set is payer- and request-type-specific; the cleanest way to avoid surprises is to send every field returned by the schema's [required-inputs endpoint](/guides/schemas#look-up-required-and-optional-inputs) plus any payer-specific fields your account team has called out.

## Worked example

A request that gets several things wrong at once:

```json theme={null}
// Request
{
  "schemaId": "fWxzG4nqtpHsJxS5Lm3q",
  "inputs": {
    "payerName": "Anthem BCBS",
    "memberId": "ABC",
    "phoneNumber": "555-12",
    "claimChargeAmount": "1.2e3",
    "dateOfService": "2026-03-15"
  }
}
```

```json theme={null}
// 400 response
{
  "error": "INVALID_INPUTS",
  "message": "Required inputs are missing or invalid.",
  "details": {
    "missingInputs": ["providerNpi"],
    "invalidInputs": {
      "memberId": "memberId 'ABC' is too short to be a BCBS member ID (got 3 characters, minimum is 9). BCBS Federal IDs are 9 chars (R + 8 digits); commercial IDs are typically 11+.",
      "phoneNumber": "phoneNumber is not a valid U.S. phone number",
      "claimChargeAmount": "claimChargeAmount is scientific notation"
    }
  }
}
```

`dateOfService` passed and `providerNpi` was simply absent (and required), so it shows up under `missingInputs` while the rest are format failures under `invalidInputs`. Read both buckets. A single resubmission should fix everything at once.
