openapi: 3.0.3

info:
  title: LoanData API
  version: "1.0"
  description: |
    Secure REST endpoint for submitting loan data electronically.
    Submitted loans are validated and preserved for downstream processing.
  contact:
    name: RT Resolutions API Support
  license:
    name: Proprietary

servers:
  - url: https://api.rtresolutions.com
    description: |
      Production base URL. The actual URL is provided to each client during
      onboarding alongside their API key. (A separate dev/sandbox URL is
      provided to clients running integration tests; that URL is delivered
      via secure channel, not in this public spec.)

security:
  - ApiKeyAuth: []

tags:
  - name: Loans
    description: Loan submission

paths:
  /api/v1/loans:
    post:
      tags: [Loans]
      summary: Submit a single loan record
      description: |
        Submits one loan record. The full request body is preserved
        verbatim for downstream processing.
      operationId: submitLoan
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/LoanSubmission'
            examples:
              sample:
                $ref: '#/components/examples/SampleLoanSubmission'
      responses:
        '201':
          description: Submission accepted and stored
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SubmissionAccepted'
        '400':
          description: Empty body or malformed JSON
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/BadRequestError'
        '403':
          description: |
            Returned by API Gateway when the `x-api-key` header is missing
            or the value is invalid. The response body is generated by API
            Gateway and is not customized.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/GatewayError'
              example:
                message: Forbidden
        '422':
          description: Validation failed; one or more rules rejected the body
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ValidationError'
        '429':
          description: |
            Rate limit exceeded. The usage plan throttles to 50 req/sec
            steady-state with a burst of 100. Generated by API Gateway.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/GatewayError'
              example:
                message: Limit Exceeded
        '503':
          description: Storage write failed; the submission was not persisted. Retry recommended (exponential backoff)
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ServiceUnavailableError'

components:

  securitySchemes:
    ApiKeyAuth:
      type: apiKey
      in: header
      name: x-api-key
      description: API key issued during onboarding.

  examples:
    SampleLoanSubmission:
      value:
        loanId: "0002034000"
        armLoan: 0
        amTerm: 360
        loanCloseDate: "2005-07-14"
        dueDate: "2006-06-01"
        firstDueDate: "2005-09-01"
        interestMethod: 0
        interestRate: 0.0525
        lcBal: 2460.00
        legalFeeBal: 0.00
        secondMtg: 0
        loanType: 0
        maturityDate: "2035-08-01"
        balloonLoan: 0
        nsfFeeBal: 0.00
        originalAmt: 150000.00
        originalTerm: 360
        othFeeBal: 0.00
        piPmt: 804.62
        pmtFrequency: 0
        previousLnNum: "5000020621"
        principalBal: 26580.83
        dayDue: 1
        partialPaid: 0.00
        intOnlyTerm: 0
        deferredPrincipal: 0.00
        deferredInterest: 0.00
        balloonPmt: 0.00
        leadBillDays: 15
        addressLine1: "2301 WINDSONG TRL"
        city: "ROUND ROCK"
        state: "TX"
        zip: "78664-7030"
        occupancy: 0
        propertyType: "SF"
        graceDays: 15
        lcType: 0
        lcPercent: 0.05
        lcMaximum: 100.00
        lcMinimum: 10.00
        lcFixedAmt: 0.00
        amtOFLast: 0.00
        b1FirstMiddleName: "DONTAE B"
        b1LastName: "CAMPBELL"
        b1TaxIDNumber: "123-45-6789"
        b1Deceased: false
        b1MiddleName: "B"
        b1NameSuffix: 0
        b2FirstMiddleName: "ALI Z"
        b2LastName: "MITCHELL"
        b2Deceased: false
        b2MiddleName: "Z"
        b2NameSuffix: 0
        b1PhoneType: 1
        b1PhoneNumber: "555-1234"
        b1AreaCode: "512"
        b1WorkPhone: 0
        b2PhoneType: 0
        b2PhoneNumber: "555-5678"
        b2WorkPhone: 0
        accrualAmount: 125.50
        thruDate: "2006-05-01"
        currentRate: 0.0525
        b1EmailAddress: "dontae.campbell@example.com"
        b1EmailAddressVerified: true
        b2EmailAddressVerified: false
        clientBalance: 26580.83
        interest: 1500.00
        fees: 0.00
        credits: 0.00
        totalBalance: 28080.83
        investorLoanID: "INV00001"

  schemas:

    # ─── Response schemas ────────────────────────────────────────────

    SubmissionAccepted:
      type: object
      required: [loanId, submissionId]
      properties:
        loanId:
          type: string
          description: |
            Echoed back from the request body so the response is self-describing.
            Use this to correlate the response with the loan you submitted
            (especially in batch/async scenarios).
          example: "0002034000"
        submissionId:
          type: string
          format: uuid
          description: |
            Server-generated UUID for this submission. Use this in any
            support inquiries about a specific submission.
          example: f55e5912-eb5d-4504-ba8a-5718e99de501

    BadRequestError:
      type: object
      required: [error]
      properties:
        error:
          type: string
          example: Invalid JSON body
        detail:
          type: string
          description: Underlying parser message; present when available
          example: "Expected end of file but got '}'."

    ValidationError:
      type: object
      required: [errors]
      properties:
        errors:
          type: array
          items:
            type: object
            required: [field, message]
            properties:
              field:
                type: string
                description: Offending field name (camelCase)
                example: loanId
              message:
                type: string
                example: LoanID is required and cannot be null or empty

    ServiceUnavailableError:
      type: object
      required: [error]
      properties:
        error:
          type: string
          example: Storage unavailable, please retry

    GatewayError:
      type: object
      description: Default error shape emitted by AWS API Gateway (auth, throttling)
      properties:
        message:
          type: string

    # ─── Request schema ──────────────────────────────────────────────

    LoanSubmission:
      type: object
      description: |
        Single loan record. Field names use camelCase. All fields are
        nullable unless explicitly listed as required.
      required:
        - loanId
        - armLoan
        - amTerm
        - dueDate
        - firstDueDate
        - interestMethod
        - interestRate
        - lcBal
        - legalFeeBal
        - secondMtg
        - loanType
        - maturityDate
        - balloonLoan
        - nsfFeeBal
        - originalAmt
        - originalTerm
        - othFeeBal
        - piPmt
        - pmtFrequency
        - principalBal
        - dayDue
        - partialPaid
        - intOnlyTerm
        - deferredPrincipal
        - deferredInterest
        - balloonPmt
        - leadBillDays
        - occupancy
        - graceDays
        - lcType
        - lcPercent
        - lcMaximum
        - lcMinimum
        - lcFixedAmt
        - amtOFLast
        - b1Deceased
        - b1NameSuffix
        - b2Deceased
        - b2NameSuffix
        - b1PhoneType
        - b1PhoneNumber
        - b1WorkPhone
        - b2PhoneType
        - b2PhoneNumber
        - b2WorkPhone
        - accrualAmount
        - thruDate
        - currentRate
        - b1EmailAddressVerified
        - b2EmailAddressVerified
        - clientBalance
        - interest
        - fees
        - credits
        - totalBalance
      properties:

        # ── Loan fields ──
        loanId:           { type: string, maxLength: 10, description: Unique loan identifier }
        armLoan:          { type: integer, minimum: 0, maximum: 1, description: Adjustable rate mortgage flag }
        amTerm:           { type: integer, description: Amortization term in months }
        lastBillDate:     { type: string, format: date, nullable: true }
        lastPmtRecvdDate: { type: string, format: date, nullable: true }
        loanCloseDate:    { type: string, format: date, nullable: true }
        dueDate:          { type: string, format: date, description: Current due date }
        firstDueDate:     { type: string, format: date, description: First payment due date }
        interestMethod:
          type: integer
          enum: [0, 1, 2, 3, 4]
          description: |
            Interest calculation method:
            * 0 = Arrears
            * 1 = Advance
            * 2 = Interest Only
            * 3 = Daily/360
            * 4 = Daily/Actual
        interestRate:     { type: number, minimum: 0, description: "Decimal (e.g., 0.0525 for 5.25%)" }
        lcBal:            { type: number, minimum: 0, description: Late charge balance }
        legalFeeBal:      { type: number, minimum: 0 }
        secondMtg:
          type: integer
          enum: [0, 1, 2, 4]
          description: |
            Lien position:
            * 0 = 1st Lien
            * 1 = 2nd Lien
            * 2 = 3rd Lien
            * 4 = 5th Lien / Unsecured
        loanType:
          type: integer
          minimum: 0
          maximum: 22
          description: |
            Loan type code (0-22). See LoanType enum table:
            0 = Conventional, 1 = FHA, 2 = VA, 3 = Other, 4 = HELOC,
            5 = Commercial, 6 = Construction, 7 = Unsecured,
            8 = Commercial Construction, 9 = Consumer, 10 = USDA,
            11 = Line of Credit, 12 = Home Equity, 13 = Student Loan,
            14 = Reverse Mortgage, 15 = Timeshare,
            16 = Construction to Perm, 17 = Commercial Construction to Perm,
            18 = Recreational Merchandise, 19 = UVR-Vis, 20 = UVR-No-Vis,
            21 = Peso-Vis, 22 = Peso-No-Vis
        maturityDate:     { type: string, format: date }
        balloonLoan:      { type: integer, minimum: 0, maximum: 1 }
        modificationDate: { type: string, format: date, nullable: true }
        nextCouponDate:   { type: string, format: date, nullable: true }
        nsfFeeBal:        { type: number, minimum: 0 }
        originalAmt:      { type: number, minimum: 0 }
        originalTerm:     { type: integer }
        othFeeBal:        { type: number, minimum: 0 }
        piPmt:            { type: number, minimum: 0, description: Principal & interest payment }
        pmtFrequency:
          type: integer
          enum: [0, 1, 2, 3, 4, 5, 6]
          description: |
            * 0 = Monthly
            * 1 = Bi-Weekly
            * 2 = Annually
            * 3 = Quarterly
            * 4 = Semi-Annually
            * 5 = Twice Monthly
            * 6 = Weekly
        previousLnNum:    { type: string, maxLength: 20, nullable: true }
        principalBal:     { type: number, minimum: 0 }
        dayDue:           { type: integer, description: Day of month payment is due }
        partialPaid:      { type: number, minimum: 0 }
        fundingDate:      { type: string, format: date, nullable: true }
        intOnlyTerm:      { type: integer, description: Interest-only term in months }
        deferredPrincipal: { type: number, minimum: 0 }
        deferredInterest: { type: number, minimum: 0 }
        balloonDate:      { type: string, format: date, nullable: true }
        balloonPmt:       { type: number, minimum: 0 }
        leadBillDays:     { type: integer }

        # ── Property fields ──
        addressLine1: { type: string, maxLength: 40, nullable: true }
        addressLine2: { type: string, maxLength: 40, nullable: true }
        city:         { type: string, maxLength: 25, nullable: true }
        state:        { type: string, maxLength: 5,  nullable: true }
        zip:          { type: string, maxLength: 10, nullable: true }
        occupancy:
          type: integer
          enum: [0, 1, 2, 3, 4, 5]
          description: |
            * 0 = Owner Occupied
            * 1 = Non-Owner Occupied
            * 2 = Vacant
            * 3 = Adverse Occupant
            * 4 = Unknown / Other
            * 5 = Abandoned
        propertyType: { type: string, maxLength: 2, nullable: true }

        # ── Delinquent fields ──
        graceDays:       { type: integer }
        lcType:
          type: integer
          enum: [0, 1, 2, 3, 4, 5, 6]
          description: |
            * 0 = % of P&I Payment
            * 1 = % of Total Payment
            * 2 = % of Net Payment
            * 3 = Fixed Amount
            * 4 = % of Principal Balance
            * 5 = No Late Charge
            * 6 = % P&I Payment + Escrow
        lcPercent:       { type: number, minimum: 0 }
        lcMaximum:       { type: number, minimum: 0 }
        lcMinimum:       { type: number, minimum: 0 }
        lcFixedAmt:      { type: number, minimum: 0 }
        dateLastCharged: { type: string, format: date, nullable: true }
        amtOFLast:       { type: number, minimum: 0 }

        # ── Borrower 1 fields ──
        b1FirstMiddleName:  { type: string, maxLength: 20, nullable: true }
        b1HomePhoneNumber:  { type: string, maxLength: 14, nullable: true }
        b1LastName:         { type: string, maxLength: 55, nullable: true }
        b1TaxIDNumber:      { type: string, maxLength: 11, nullable: true, description: SSN/ITIN format XXX-XX-XXXX }
        b1WorkPhoneNumber:  { type: string, maxLength: 14, nullable: true }
        b1BirthDate:        { type: string, format: date, nullable: true }
        b1Deceased:         { type: boolean }
        b1DateDeceased:     { type: string, format: date, nullable: true }
        b1Language:         { type: string, maxLength: 3, nullable: true, description: Language code; see Language enum }
        b1WorkEmail:        { type: string, maxLength: 50, nullable: true }
        b1EmailConfirmDate: { type: string, format: date, nullable: true }
        b1MiddleName:       { type: string, maxLength: 20, nullable: true }
        b1NameSuffix:       { type: integer, description: 0 = none }

        # ── Borrower 2 fields ──
        b2FirstMiddleName:  { type: string, maxLength: 20, nullable: true }
        b2HomePhoneNumber:  { type: string, maxLength: 14, nullable: true }
        b2LastName:         { type: string, maxLength: 55, nullable: true }
        b2TaxIDNumber:      { type: string, maxLength: 11, nullable: true }
        b2WorkPhoneNumber:  { type: string, maxLength: 14, nullable: true }
        b2BirthDate:        { type: string, format: date, nullable: true }
        b2Deceased:         { type: boolean }
        b2DateDeceased:     { type: string, format: date, nullable: true }
        b2Language:         { type: string, maxLength: 3, nullable: true }
        b2WorkEmail:        { type: string, maxLength: 50, nullable: true }
        b2EmailConfirmDate: { type: string, format: date, nullable: true }
        b2MiddleName:       { type: string, maxLength: 20, nullable: true }
        b2NameSuffix:       { type: integer }

        # ── Borrower 1 phone ──
        b1PhoneType:
          type: integer
          enum: [0, 1, 2, 3, 4, 5]
          description: |
            * 0 = Land
            * 1 = Cell
            * 2 = Pager
            * 3 = Fax
            * 4 = VoiceMail
            * 5 = VOIP
        b1PhoneNumber: { type: string, maxLength: 14, description: Phone number (XXX-XXXX) }
        b1AreaCode:    { type: string, maxLength: 3, nullable: true }
        b1WorkPhone:   { type: integer, minimum: 0, maximum: 1 }

        # ── Borrower 2 phone ──
        b2PhoneType:
          type: integer
          enum: [0, 1, 2, 3, 4, 5]
        b2PhoneNumber: { type: string, maxLength: 14 }
        b2AreaCode:    { type: string, maxLength: 3, nullable: true }
        b2WorkPhone:   { type: integer, minimum: 0, maximum: 1 }

        # ── Mailing address ──
        mailingAddressLine1: { type: string, maxLength: 40, nullable: true }
        mailingAddressLine2: { type: string, maxLength: 40, nullable: true }
        mailingCity:         { type: string, maxLength: 25, nullable: true }
        mailingState:        { type: string, maxLength: 25, nullable: true }
        mailingZip:          { type: string, maxLength: 10, nullable: true }

        # ── Accrued interest ──
        accrualAmount: { type: number, minimum: 0 }
        thruDate:      { type: string, format: date }
        currentRate:   { type: number, minimum: 0 }

        # ── Borrower email ──
        b1EmailAddress:         { type: string, maxLength: 50, nullable: true }
        b1EmailAddressVerified: { type: boolean }
        b2EmailAddress:         { type: string, maxLength: 50, nullable: true }
        b2EmailAddressVerified: { type: boolean }

        # ── Misc / creditor ──
        currentCreditor:                { type: string, maxLength: 300, nullable: true }
        originalCreditor:               { type: string, maxLength: 300, nullable: true }
        originalCreditorAddressLine1:   { type: string, maxLength: 40,  nullable: true }
        originalCreditorAddressLine2:   { type: string, maxLength: 40,  nullable: true }
        originalCreditorCity:           { type: string, maxLength: 25,  nullable: true }
        originalCreditorState:          { type: string, maxLength: 25,  nullable: true }
        originalCreditorZip:            { type: string, maxLength: 10,  nullable: true }
        currentCreditorAddressLine1:    { type: string, maxLength: 40,  nullable: true }
        currentCreditorAddressLine2:    { type: string, maxLength: 40,  nullable: true }
        currentCreditorCity:            { type: string, maxLength: 25,  nullable: true }
        currentCreditorState:           { type: string, maxLength: 25,  nullable: true }
        currentCreditorZip:             { type: string, maxLength: 10,  nullable: true }
        currentCreditorPhoneNumber:     { type: string, maxLength: 20,  nullable: true }

        # ── Previous client data ──
        balanceDate:    { type: string, format: date, nullable: true }
        reconType:      { type: string, maxLength: 15, nullable: true, description: 'One of Chargeoff / LastPayment / LastStatement' }
        clientBalance:  { type: number }
        creditor:       { type: string, maxLength: 500, nullable: true }
        interest:       { type: number }
        fees:           { type: number }
        credits:        { type: number }
        totalBalance:   { type: number }

        # ── Participation ──
        investorLoanID: { type: string, maxLength: 12, nullable: true }
