Integration handoff

Maia read-only credit retrieval

Dedicated revocable credential, verified intake field, 12 synthetic fixtures and the nine-attribute mapping. Synthetic data only, no bureau inquiries, no lender submissions, no client communications.

HMB outbound hold preserved
Scenario 3690809 inactive
Synthetic only
Contract v2.0
Downloads

Fixtures carry the expected status, the credential state under test, the request and the sanitized response. No credential value appears in any file.

Credential domain
  • Header: Authorization: Bearer pfmaia.<key_id>.<secret>
  • Only scope: read:credit_result. It cannot pull credit, submit to a lender, or reach any other function.
  • Stored as a salted HMAC-SHA256 hash. Plaintext shown once at create/rotate, never again.
  • Revoke or rotate per credential, without touching any other integration.
  • INTERNAL_AUTOMATION_SECRET is not exposed and is not rotated for this work.
Make authentication

The token belongs in a Make connection, never in the blueprint: HTTP → Make an API Key Auth request, parameter Authorization, value Bearer pfmaia.…, placement header.

Acceptance check before activation: the exported blueprint contains zero pfmaia. occurrences and the module references an opaque connection id.

PushFi cannot read Make blueprints, so this check is operator-attested, not verified here.

Intake response field (verified)

makeLeadsApi returns loan_application_id as a top-level field of the response body, for both submission types.

Make path: {{1.loan_application_id}} when the intake module is module 1. It is the system UUID, not the display application number.

{
  "success": true,
  "submission_type": "intake",
  "customer_id": "fx-cust-0001",
  "loan_application_id": "fx-app-0001",
  "created_or_updated": "created",
  "matched_by": "owner_email"
}
Field mapping — nine attributes to Make inputs
6 missing
2 incompatible
1 partial
2 map cleanly

The nine returned attributes

PushFi fieldTypeExisting Make inputVerdictNote
revolving_accountsint (count)none (revolving_balances = $, credit_utilization = %)incompatibleName-similar, meaning different. Mapping a count into revolving_balances would corrupt debt figures.
mortgage_accountsint (count)nonemissingNo input slot exists.
derogatoriesint (count)credit_profile.negative_itemspartialSafe only if consumed as a plain count: PushFi counts collections and late payments separately, so mapping all three double-counts.
collections_countint (count)none distinct (absorbed by negative_items)missingCannot be recovered downstream once folded into a single count.
late_payments_countint (count)none distinct (absorbed by negative_items)missingSame constraint as collections_count.
bankruptcybooleannonemissingCannot be inferred from a count.
foreclosurebooleannonemissingCannot be inferred from a count.
monthly_debt_obligationsnumber (currency)nonemissingNo debt-service input exists today.
unsecured_debtnumber (currency)none (revolving_balances writes to business_liens_total)incompatibleA different concept. Must not be aliased to revolving_balances.

Companion fields & reverse gaps

PushFi fieldTypeExisting Make inputVerdictNote
credit_scorecredit_profile.fico_scorecompatibleSame meaning.
bureaucredit_profile.bureaucompatibleProvider label.
acquired_at / age_days / valid_throughnonemissingNo freshness input. Gate on available + reusable instead of recomputing age.
credit_profile.inquiries (Make side)— not returned by PushFi —unavailableKeep Make's own value; do not derive it. Extend the contract deliberately if needed.
credit_profile.credit_tier (Make side)— not returned by PushFi —unavailablePushFi returns the score, not a band. Derive the tier or keep the existing source.
Endpoint & error handling
POST https://push-fi-loan-orchestration-d379ddba.base44.app/functions/getExistingCreditResult
Content-Type: application/json
Authorization: Bearer pfmaia.<key_id>.<secret>

{ "application_id": "fx-app-0001",
  "owner_applicant_id": "fx-owner-0001" }
HTTPReasonHandling
400INVALID_REQUESTFix the mapping, do not retry
401INVALID_CREDENTIALDo not retry, re-bind connection
401CREDENTIAL_REVOKED / INACTIVE / EXPIREDStop, request rotation
403SCOPE_NOT_PERMITTEDStop, wrong credential
403APPLICATION_NOT_PERMITTEDStop, expected refusal
404APPLICATION_NOT_FOUNDCreate nothing, fix the id
429RATE_LIMITEDWait Retry-After, then retry
500SERVER_ERRORRetry once after 30s, then escalate

Idempotency: pure read, always writes_performed: 0. Repeating a request returns the same underlying result. Retry only on 429 (after Retry-After) or once on 5xx. Read before pull: available: false routes to human review, never to an automatic pull.

Test evidence (synthetic)

testMaiaReadOnlyCredentialContract

16 / 16 passed · provider calls 0 · Synthetic rows only, removed after run

testCreditPullAuthAndReuse

23 / 23 passed · provider calls 0 · 0 writes performed

Covered: missing/wrong/revoked/expired credential, scope and allowlist refusal, rotation, rate limit, credential-domain separation, PII-free audit on allowed and denied attempts, and a mutation guard proving application, credit-snapshot, checkpoint, acquisition and authorization counts are unchanged.

Open items before go-live
  1. Make blueprint certificate for the connection binding (operator must attest).
  2. Decide the negative_items convention, then add the seven missing Make inputs.
  3. inquiries and credit_tier are not returned by this endpoint — keep Make's own source or extend the contract deliberately.
  4. Narrow the credential from application_scope: all to an allowlist before general rollout.
  5. Fixtures 1–4 need a synthetic application created for the test; they are not replayable against a real applicant file.