HETIBANK SDK Read the docs

API guide

The SDK is generated against the canonical HETIBANK OpenAPI 3.1 contract.

Contract

  • spec/hetibank.openapi.yaml — the canonical spec, copied unchanged from the application

(lib/api-spec/openapi.yaml: 301 operations, 18 tags).

  • spec/public-openapi.json — the buyer-facing projection served by the API.
  • spec/fixtures/ — contract-valid example payloads used by SDK tests.
  • spec/sdk-surface.md — the curated operations the SDK exposes, with auth, errors and pagination.

The spec is the source of truth. If a type in this repo disagrees with the spec, the spec wins and we fix the SDK.

Authentication

CallerCredentialNotes
Signed-in buyer (browser)buyer session bearer token + X-Hetibank-Key browser keythe API never creates accounts; the buyer signs in on the HETIBANK funnel
Server integrationsecret API keynever ship a secret key in a browser bundle, widget or plugin
Testhbk_test_* keyreturns sandbox homes only
import { createHetibankClient } from "@hetibank/client";

const client = createHetibankClient({
  baseUrl: "https://www.hetibank.com/api",
  getToken: () => session.bearerToken,       // refreshed by your auth layer
  getApiKey: () => process.env.HETIBANK_BROWSER_KEY, // public browser key, not a secret
  fetch: globalThis.fetch,                  // optional: inject a proxy/recording transport
});

The client takes a transport, not a global fetch monkey-patch. This is how a server-side proxy, an Electron shell or a test recorder plugs in without the packages knowing.

Errors

One envelope everywhere:

{ "code": "rate_limited", "message": "Too many requests", "request_id": "req_01J..." }

The client throws HetibankApiError with status, code, requestId, retryAfterSeconds and retryable. It retries 429, 502, 503 and 504 with backoff honoring Retry-After, and never retries a 4xx that is not 429. UI error states display the request_id so support can correlate.

Figures and evidence

Every money/percent/count value in a buyer-UI response is a Figure:

{
  "value": 2940,
  "unit": "usd_per_month",
  "certainty": "estimated",
  "source": "hetibank_model",
  "asOf": "2026-11-01",
  "note": "Includes estimated taxes and insurance"
}
  • certainty: known | estimated | unverified | missing
  • missing always has value: null; render it as “Unavailable”
  • source follows the loan evidence ladder (`mls_model < title_record < statement_supported <

servicer_confirmed`)

  • computed values should also carry a definitionId as the API adds it; show it in the evidence chip

The SDK never recomputes these. If you need a payment, ask the API for the component.

Buyer-UI envelopes

The API returns canonical components, not ad-hoc JSON:

{
  "contract": "hetibank.buyer-ui.v1",
  "component": "financing_comparison",
  "summary": "Assuming the existing FHA loan saves an estimated $312/month",
  "asOf": "2026-11-01",
  "nextAction": { "key": "view_evidence", "label": "See loan evidence", "path": "/property/ABC123/evidence" },
  "data": { "rows": [ … ], "estimatedMonthlySavings": { … }, "assumptions": [ … ] }
}

Kinds: property_card, financing_comparison, payment_breakdown, cash_to_close, market_snapshot, buyer_boost_progress, saved_search_card. Validate with buyerUiComponentSchema before rendering.

Search and the trust layer

Search responses should carry the filters actually applied and any criteria the engine relaxed:

{
  "applied": { "applied": [ … ], "relaxed": [{ "key": "maxMonthlyPayment", "requested": 2800, "applied": null, "reason": "no_inventory", "message": "No homes under $2,800; showing from $2,940" }], "geographySearched": "Henderson, NV" }
}

Show relaxed in the UI. A silently widened search is a trust failure, not a convenience.

The buyer search request shape is in packages/contracts/src/search.ts (searchRequestSchema): view (assumption | traditional), criteria.buyerScenario (cash, max monthly payment), criteria.propertyFilters, criteria.assumptionFilters (max rate in bps, null = no cap), sort, limit, cursor.

Pagination and rate limits

  • Lists use limit + cursor; do not page by offset.
  • 429 includes Retry-After; the client honors it and exposes retryAfterSeconds.
  • Do not parallelize assumption and traditional searches to “cover” an ambiguous query — route from

the stated intent.

Response size

The MCP surface has a token budget, so tool responses cap lists and omit fields the agent cannot use. REST responses may be richer. Do not assume parity of payload size between REST and MCP — assume parity of figures.

Regenerating types

The SDK's contract tests pin the spec hash and validate fixtures. When the application spec changes:

  1. Update spec/hetibank.openapi.yaml and note the source commit in spec/README.md.
  2. Regenerate types (pnpm --filter @hetibank/client generate if the script is present).
  3. Run node tools/pm/pm.mjs check client-build contracts-test and pnpm test.
  4. Update docs/PROVENANCE.md and CHANGELOG.md.

A spec change without a fixture and a passing contract test is not a contract change yet.