Validation Rules & Business Logic

Comprehensive guide to field-level validation rules, business logic constraints, and status transitions.

Validation Rules and Business Logic

📘

Data Quality & Compliance

This document provides comprehensive validation rules for all API fields, business logic constraints, and status transition rules to ensure data quality and regulatory compliance.

Understanding validation rules helps you build robust integrations that minimize errors and streamline processing.


Validation Overview

%%{init: {'theme': 'base', 'themeVariables': {'background': '#ffffff', 'mainBkg': '#ffffff', 'clusterBkg': '#f9fafb', 'clusterBorder': '#2563eb', 'primaryColor': '#2563eb', 'primaryTextColor': '#ffffff', 'titleColor': '#000000', 'lineColor': '#374151'}, 'flowchart': {'padding': 25, 'nodeSpacing': 45, 'rankSpacing': 50}}}%%
flowchart LR
    subgraph VAL[📋 Validation Layers]
        A[📝 Field Format] --> B[📊 Business Rules]
        B --> C[🔄 State Machine]
        C --> D[✅ Valid Request]
    end
    
    style A fill:#2563eb,stroke:#1e40af,color:#fff
    style B fill:#d97706,stroke:#b45309,color:#fff
    style C fill:#2563eb,stroke:#1e40af,color:#fff
    style D fill:#059669,stroke:#047857,color:#fff
Validation LayerDescriptionExample
Field FormatData type, format, lengthSSN must be 9 digits
Business RulesLogic constraintsLoan amount ≤ product max
State MachineValid transitionsDraft → Submitted (valid)

Field-Level Validation Rules

Loan Amount

PropertyValue
Fieldloan_amount
Typenumber
Required✅ Yes

Validation Rules:

RuleValue
Minimum$1,000
MaximumVaries by product (typically $50,000 - $500,000)
PrecisionUp to 2 decimal places
FormatPositive number only

Error Codes:

CodeDescription
INVALID_LOAN_AMOUNTAmount outside valid range
LOAN_AMOUNT_EXCEEDS_LIMITAmount exceeds product maximum

Examples:

// ✅ Valid
{ "loan_amount": 50000 }

// ❌ Invalid - below minimum
{ "loan_amount": 500 }

// ❌ Invalid - exceeds typical product limit
{ "loan_amount": 1000000 }

Interest Rate

PropertyValue
Fieldinterest_rate
Typenumber
Required✅ Yes (for certain products)

Validation Rules:

RuleValue
Minimum0.00%
Maximum25.00%
PrecisionUp to 4 decimal places
FormatPercentage (e.g., 5.5 for 5.5%)

Error Code: INVALID_INTEREST_RATE


Loan Term

PropertyValue
Fieldloan_term
Typeinteger
Required✅ Yes

Validation Rules:

RuleValue
Minimum12 months
Maximum360 months (30 years)
FormatInteger only

Error Code: INVALID_LOAN_TERM


SSN (Social Security Number)

PropertyValue
Fieldssn
Typestring
Required✅ Yes (for individual borrowers)

Validation Rules:

RuleValue
FormatXXX-XX-XXXX or XXXXXXXXX
Length9 digits
PatternValid SSN format

Error Codes:

CodeDescription
INVALID_SSN_FORMATFormat incorrect
INVALID_SSNSSN validation failed

Examples:

// ✅ Valid - with hyphens
{ "ssn": "123-45-6789" }

// ✅ Valid - without hyphens
{ "ssn": "123456789" }

// ❌ Invalid - wrong length
{ "ssn": "123-45-67" }

Email Address

PropertyValue
Fieldemail
Typestring
Required⚠️ Conditional (required for portal access)

Validation Rules:

RuleValue
FormatRFC 5322 valid email
Max Length255 characters
DomainMust be valid domain

Error Code: INVALID_EMAIL_FORMAT


Phone Number

PropertyValue
Fieldphone
Typestring
Required✅ Yes

Validation Rules:

RuleValue
Format(XXX) XXX-XXXX or XXX-XXX-XXXX or XXXXXXXXXX
Length10 digits (US format)
PatternValid US phone number

Error Code: INVALID_PHONE_FORMAT


Date Fields

PropertyValue
Fieldsapplication_date, birth_date, loan_start_date, etc.
Typestring (ISO 8601)
Required⚠️ Varies by field

Validation Rules:

RuleValue
FormatYYYY-MM-DD or YYYY-MM-DDTHH:mm:ssZ
RangeMust be valid calendar date

Business Rules:

FieldRule
birth_dateMust be in the past
loan_start_dateMust be today or future
application_dateCannot be more than 30 days in future

Error Codes:

CodeDescription
INVALID_DATE_FORMATFormat incorrect
INVALID_DATE_RANGEDate outside valid range

Examples:

// ✅ Valid - ISO 8601 date
{ "loan_start_date": "2025-12-20" }

// ✅ Valid - ISO 8601 datetime
{ "created_at": "2025-12-15T10:30:00Z" }

// ❌ Invalid - wrong format
{ "application_date": "12/15/2025" }

Business Rule Validations

Loan Amount vs Product Limits

Rule: Loan amount cannot exceed product maximum limit

def validate_loan_amount(loan_amount, product):
    """Validate loan amount against product limits."""
    if loan_amount < product.min_amount:
        raise ValidationError(
            code="LOAN_AMOUNT_BELOW_MINIMUM",
            message=f"Loan amount ${loan_amount} below minimum ${product.min_amount}",
            details={"min_amount": product.min_amount, "provided": loan_amount}
        )
    
    if loan_amount > product.max_amount:
        raise ValidationError(
            code="LOAN_AMOUNT_EXCEEDS_LIMIT",
            message=f"Loan amount exceeds product maximum of ${product.max_amount}",
            details={"max_amount": product.max_amount, "provided": loan_amount}
        )

Debt-to-Income Ratio (DTI)

Rule: DTI ratio must be below product threshold (typically 43%)

def validate_dti_ratio(total_monthly_debt, monthly_income, max_dti=43):
    """Validate debt-to-income ratio."""
    if monthly_income <= 0:
        raise ValidationError(
            code="INVALID_INCOME",
            message="Monthly income must be greater than zero"
        )
    
    dti_ratio = (total_monthly_debt / monthly_income) * 100
    
    if dti_ratio > max_dti:
        raise ValidationError(
            code="DTI_TOO_HIGH",
            message=f"DTI ratio {dti_ratio:.1f}% exceeds maximum {max_dti}%",
            details={"dti_ratio": dti_ratio, "max_dti": max_dti}
        )
    
    return dti_ratio

Credit Score Requirements

Rule: Credit score must meet product minimum

def validate_credit_score(credit_score, product):
    """Validate credit score against product requirements."""
    if credit_score < product.min_credit_score:
        raise ValidationError(
            code="CREDIT_SCORE_TOO_LOW",
            message=f"Credit score {credit_score} below minimum {product.min_credit_score}",
            details={
                "credit_score": credit_score,
                "min_required": product.min_credit_score
            }
        )

Collateral Requirements

Rule: Collateral value must be sufficient for loan amount

def validate_collateral(loan_amount, collateral_value, ltv_max=80):
    """Validate loan-to-value ratio."""
    if collateral_value <= 0:
        raise ValidationError(
            code="INVALID_COLLATERAL_VALUE",
            message="Collateral value must be greater than zero"
        )
    
    ltv_ratio = (loan_amount / collateral_value) * 100
    
    if ltv_ratio > ltv_max:
        raise ValidationError(
            code="INSUFFICIENT_COLLATERAL",
            message=f"LTV ratio {ltv_ratio:.1f}% exceeds maximum {ltv_max}%",
            details={
                "ltv_ratio": ltv_ratio,
                "max_ltv": ltv_max,
                "required_collateral": loan_amount / (ltv_max / 100)
            }
        )

Required vs Optional Fields

Application Submission APIs (LOS)

Required Fields

FieldTypeDescription
borrower_namestringFull name of borrower
loan_amountnumberRequested loan amount
loan_purposestringPurpose of loan
ssnstringSocial Security Number
emailstringEmail address
phonestringPhone number

Optional Fields

FieldTypeDescription
co_borrower_namestringCo-borrower name
business_namestringBusiness name (business loans)
einstringEmployer Identification Number
collateral_descriptionstringCollateral description
notesstringAdditional notes

Active Loan Servicing APIs (LMS)

Required Fields (Loan Boarding)

FieldTypeDescription
application_uidstringLOS application UID
loan_amountnumberLoan amount
interest_ratenumberInterest rate
loan_termintegerLoan term in months
loan_start_datestringLoan start date

Status Transition Rules

Application Status Transitions

%%{init: {'theme': 'base', 'themeVariables': {'background': '#ffffff', 'mainBkg': '#ffffff', 'clusterBkg': '#f9fafb', 'clusterBorder': '#2563eb', 'primaryColor': '#2563eb', 'primaryTextColor': '#ffffff', 'titleColor': '#000000', 'lineColor': '#374151'}, 'flowchart': {'padding': 25, 'nodeSpacing': 45, 'rankSpacing': 50}}}%%
flowchart LR
    subgraph APP[📋 Application Status Flow]
        A[Draft] --> B[Submitted]
        B --> C[Under Review]
        C --> D[Approved]
        C --> E[Declined]
        D --> F[Funded]
    end
    
    style A fill:#6b7280,stroke:#4b5563,color:#fff
    style B fill:#2563eb,stroke:#1e40af,color:#fff
    style C fill:#d97706,stroke:#b45309,color:#fff
    style D fill:#059669,stroke:#047857,color:#fff
    style E fill:#dc2626,stroke:#b91c1c,color:#fff
    style F fill:#059669,stroke:#047857,color:#fff

Valid Transitions:

FromToRequirements
DraftSubmittedAlways allowed
SubmittedUnder ReviewAutomatic or manual
Under ReviewApprovedAll verifications complete
Under ReviewDeclinedDecline reason required
ApprovedFundedFunding details required

Invalid Transitions:

❌ TransitionReason
DraftApprovedCannot skip statuses
ApprovedUnder ReviewCannot go backwards
DeclinedApprovedFinal state

Error Code: INVALID_STATUS_TRANSITION


Loan Status Transitions

%%{init: {'theme': 'base', 'themeVariables': {'background': '#ffffff', 'mainBkg': '#ffffff', 'clusterBkg': '#f9fafb', 'clusterBorder': '#2563eb', 'primaryColor': '#2563eb', 'primaryTextColor': '#ffffff', 'titleColor': '#000000', 'lineColor': '#374151'}, 'flowchart': {'padding': 25, 'nodeSpacing': 45, 'rankSpacing': 50}}}%%
flowchart LR
    subgraph LOAN[💰 Loan Status Flow]
        A[Boarding] --> B[Active]
        B --> C[Current]
        C --> D[Paid Off]
        B --> E[Delinquent]
        E --> F[Collections]
        F --> G[Charged Off]
        E --> C
    end
    
    style A fill:#6b7280,stroke:#4b5563,color:#fff
    style B fill:#2563eb,stroke:#1e40af,color:#fff
    style C fill:#059669,stroke:#047857,color:#fff
    style D fill:#059669,stroke:#047857,color:#fff
    style E fill:#d97706,stroke:#b45309,color:#fff
    style F fill:#dc2626,stroke:#b91c1c,color:#fff
    style G fill:#374151,stroke:#1f2937,color:#fff

Valid Transitions:

FromToTrigger
BoardingActiveBoarding complete
ActiveCurrentPayments on schedule
ActiveDelinquentPayment overdue
DelinquentCurrentPayment received
DelinquentCollectionsDays past due threshold
CollectionsCharged OffWrite-off decision
CurrentPaid OffFinal payment received

Error Response Format

All validation errors follow this format:

{
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "Request failed validation",
    "details": [
      {
        "field": "loan_amount",
        "issue": "MAX_LIMIT_EXCEEDED",
        "message": "Loan amount exceeds maximum product limit",
        "max": 50000,
        "provided": 75000
      }
    ],
    "trace_id": "a1b2c3d4e5"
  }
}

Comprehensive Validation Example

class LoanApplicationValidator:
    """Complete validation for loan applications."""
    
    def __init__(self, product_config):
        self.product = product_config
    
    def validate(self, application):
        """Run all validations on application."""
        errors = []
        
        # Field format validations
        errors.extend(self._validate_ssn(application.get('ssn')))
        errors.extend(self._validate_email(application.get('email')))
        errors.extend(self._validate_phone(application.get('phone')))
        errors.extend(self._validate_dates(application))
        
        # Business rule validations
        errors.extend(self._validate_loan_amount(application.get('loan_amount')))
        errors.extend(self._validate_dti(application))
        errors.extend(self._validate_collateral(application))
        
        if errors:
            raise ValidationError(
                code="VALIDATION_ERROR",
                message="Application validation failed",
                details=errors
            )
        
        return True
    
    def _validate_ssn(self, ssn):
        """Validate SSN format."""
        import re
        errors = []
        
        if not ssn:
            errors.append({"field": "ssn", "issue": "REQUIRED_FIELD_MISSING"})
        elif not re.match(r'^\d{3}-?\d{2}-?\d{4}$', ssn):
            errors.append({"field": "ssn", "issue": "INVALID_FORMAT"})
        
        return errors
    
    def _validate_loan_amount(self, amount):
        """Validate loan amount against product limits."""
        errors = []
        
        if not amount:
            errors.append({"field": "loan_amount", "issue": "REQUIRED_FIELD_MISSING"})
        elif amount < self.product.min_amount:
            errors.append({
                "field": "loan_amount",
                "issue": "BELOW_MINIMUM",
                "min": self.product.min_amount,
                "provided": amount
            })
        elif amount > self.product.max_amount:
            errors.append({
                "field": "loan_amount",
                "issue": "EXCEEDS_MAXIMUM",
                "max": self.product.max_amount,
                "provided": amount
            })
        
        return errors
    
    # ... additional validation methods

# Usage
validator = LoanApplicationValidator(product_config)
try:
    validator.validate(application_data)
    print("✅ Validation passed")
except ValidationError as e:
    print(f"❌ Validation failed: {e.details}")

Validation Quick Reference

FieldFormatRequiredError Code
loan_amountNumber, $1K-$500KINVALID_LOAN_AMOUNT
interest_rateNumber, 0-25%INVALID_INTEREST_RATE
loan_termInteger, 12-360INVALID_LOAN_TERM
ssnXXX-XX-XXXXINVALID_SSN_FORMAT
emailRFC 5322⚠️INVALID_EMAIL_FORMAT
phone10 digitsINVALID_PHONE_FORMAT
dateYYYY-MM-DD⚠️INVALID_DATE_FORMAT

Support

Questions about validation rules?

  • Email: [email protected]
  • API Reference: See endpoint documentation for field-specific rules

Understanding Validation?

Return to API Integration Flow to continue your integration.