Personal Loan Decisions

Score an unsecured personal loan using cash-flow underwriting. Designed for thin-file and no-file borrowers, combining Plaid bank data with Argyle income verification. Returns a TILA-compliant approval, a counter-offer, or a decline with ECOA reason codes.

Create a decision

POST/v1/decisions/personal-loan

Request body

FieldTypeDescription
consumerRequiredobjectIdentifies the consumer. See Consumer object below. Either consumer_id (for returning customers) OR full PII fields (for new consumers).
loanRequiredobjectThe loan terms requested. See Loan object below.

Consumer object

FieldTypeDescription
consumer_iduuidUUID of an existing consumer in your tenant. Skip the rest of the consumer fields if provided.
first_namestringRequired for new consumers.
last_namestringRequired for new consumers.
emailstringRequired for new consumers. Used for verification flows.
phonestringE.164 format. Used for SMS verification when KYC tier is enhanced/hard.
dobstringISO 8601 date (YYYY-MM-DD). Required for new consumers.
ssn_last4stringLast 4 digits of SSN. Required for enhanced/hard KYC. Encrypted at rest.
addressobjectMailing address. line1, city, state, zip required for new consumers.

Loan object

FieldTypeDescription
amountRequireddecimalRequested loan amount. Personal loans range from $1,000 to $50,000. Use a string to avoid floating-point issues.
currencyRequiredstringISO 4217 currency code. Currently only USD is supported.
term_monthsRequiredintegerRequested term. Personal loans support 12 to 84 months. May be capped by risk grade.
loan_purposestringdebt_consolidation, home_improvement, medical, major_purchase, or other. Informational; does not change pricing.

Example request

Request body

{
  "consumer": {
    "first_name": "Amara",
    "last_name": "Osei",
    "email": "amara@example.com",
    "phone": "+15556781234",
    "dob": "1992-09-30",
    "ssn_last4": "6677",
    "address": {
      "line1": "203 Elm St",
      "city": "Austin",
      "state": "TX",
      "zip": "78701"
    }
  },
  "loan": {
    "amount": 12000.00,
    "currency": "USD",
    "term_months": 36,
    "loan_purpose": "debt_consolidation"
  }
}

Built for thin-file borrowers

Personal loan decisions lean heavily on cash-flow signals from Plaid and income continuity from Argyle. A consumer with no traditional bureau file can still be scored from these alternative signals alone. See the personal loan product page for the full signal set.

Response

The response status depends on the KYC tier AltFiScore assigns. Smaller amounts may clear with soft KYC; larger amounts trigger enhanced or hard KYC, returning needs_consumer_action with a URL the consumer must visit.

Approved

Status complete with outcome approved. Use the monthly_payment and tila_disclosure fields to present the offer.

200 OK · approved

{
  "decision_id": "a12b3c45-6d7e-4f89-9a0b-1c2d3e4f5a6b",
  "application_id": "a12b3c45-6d7e-4f89-9a0b-1c2d3e4f5a6b",
  "status": "complete",
  "kyc_tier": "enhanced",
  "kyc_status": "matched",
  "outcome": "approved",
  "score": 762,
  "risk_grade": "B",
  "approved_amount": "12000.00",
  "currency": "USD",
  "apr": "14.25",
  "term_months": 36,
  "monthly_payment": "411.28",
  "tila_disclosure": {
    "amount_financed": "12000.00",
    "finance_charge": "2806.08",
    "total_of_payments": "14806.08",
    "apr": "14.25",
    "payment_frequency": "monthly"
  },
  "decline_reasons": [],
  "expires_at": "2026-05-15T20:30:20.566665Z"
}

Counter-offer (referred)

When the requested amount or term exceeds the limits for the assigned risk grade, AltFiScore returns outcome referred with a counter_offer. The consumer can accept the adjusted amount, APR, and term.

200 OK · counter-offer

{
  "decision_id": "b23c4d56-...",
  "application_id": "b23c4d56-...",
  "status": "complete",
  "outcome": "referred",
  "score": 668,
  "risk_grade": "C",
  "counter_offer": {
    "is_viable": true,
    "offered_amount": "7200.00",
    "offered_apr_pct": "22.00",
    "offered_term_months": 36,
    "monthly_payment": "275.14",
    "reason": "Your requested amount of $12,000 exceeds the maximum for Grade C borrowers (max $7,200)."
  },
  "decline_reasons": []
}

Declined

Status complete with outcome declined. The decline_reasons array contains ECOA-compliant codes. See compliance for guidance on adverse action handling.

200 OK · declined

{
  "decision_id": "c34d5e67-...",
  "application_id": "c34d5e67-...",
  "status": "complete",
  "outcome": "declined",
  "score": 602,
  "risk_grade": "D",
  "decline_reasons": [
    { "code": "AA-01", "reason": "Insufficient cash flow stability over the last 90 days." },
    { "code": "AA-07", "reason": "Outstanding obligations exceed approval thresholds." }
  ]
}

A decline is still a 200

The HTTP status indicates whether the API call succeeded, not whether the consumer was approved. A successfully-evaluated decline is HTTP 200 with outcome: "declined". See the errors page for genuine error cases.

Response fields

FieldTypeDescription
decision_iduuidUnique identifier for this decision. Store for reconciliation and later lookup.
application_iduuidIdentifier for the underlying application. Typically equals decision_id for first-time decisions.
statusstringcomplete or needs_consumer_action.
kyc_tierstringsoft, enhanced, or hard. Indicates the verification depth applied.
kyc_statusstringmatched, pending, or skipped.
outcomestringapproved, declined, or referred. null when status is needs_consumer_action.
scoreintegerAltFiScore from 300 to 1000.
risk_gradestringLetter grade A+ through D derived from the score.
approved_amountdecimalString. The amount AltFiScore approved. May be less than requested for risk-based offers.
aprdecimalString. Annual percentage rate as a percentage.
term_monthsintegerApproved term in months. May be shorter than requested.
monthly_paymentdecimalString. Calculated monthly installment.
tila_disclosureobjectTILA Reg Z disclosure block ready to display to the consumer.
counter_offerobjectPresent when outcome is referred. Contains adjusted amount, APR, term, and reason.
decline_reasonsarrayArray of { code, reason } objects. Populated when outcome is declined or referred.
expires_attimestampISO 8601 timestamp. The offer expires at this time.

Poll a decision

GET/v1/decisions/personal-loan/{decision_id}

Returns the current state of a previously-created decision. Idempotent and safe to call repeatedly. Recommended polling interval: 2–3 seconds.

Poll a pending decision

curl 'https://api.altfiscore.com/v1/decisions/personal-loan/a12b3c45-6d7e-4f89-9a0b-1c2d3e4f5a6b' \
  -H 'Authorization: Bearer altfi_test_YOUR_KEY'

Use idempotency

Both POST and GET endpoints are safe to retry. The POST endpoint with the same Idempotency-Key returns the cached decision. The GET endpoint is naturally idempotent.

Errors

Returns standard HTTP error codes. The full catalog is in the errors reference. Most common errors for this endpoint:

  • 400 invalid_request — required field missing
  • 401 invalid_api_key — bad or missing API key
  • 409 idempotency_conflict — same idempotency key with a different body
  • 422 unprocessable_entity — request is well-formed but fails business rules (e.g., amount below $1,000 minimum)

Next steps