# HACS FI API > Consent-gated B2B credit-score API for financial institutions (soft-pull only). > Deny-by-default: data is returned only under an ACTIVE user consent bound to > (client, purpose). The only subject identifier on this surface is the partner > pairwise subject identifier (pairwiseSub) — internal ids are never exposed. Key facts: - Score scale: 0-850 (max 850). Tier cuts: 750/650/550/450 — EXCELLENT 750-850, GOOD 650-749, FAIR 550-649, BELOW_AVERAGE 450-549, POOR 0-449. - Reason codes: catalog of 31, at most 4 per response, ranked by impact. - Attestation (score credential) validity: 90 days from issuance. - Rate limit: 60 req/min; HTTP 429 carries a Retry-After header (seconds). - Base URL: https://trendy.storydot.kr/superapp/api (LIVE and SANDBOX share it; the environment is bound to the API key). - Auth headers on every request: X-Client-Id and X-API-Key. - Standard response envelope: { "success", "data", "message" } with "errorCode" on failure. - Webhook signature: X-HACS-Signature: t=,v1= plus X-HACS-Delivery-Id; retry ladder 1m/5m/30m/2h; https endpoints only. Endpoints (FI surface): - POST /fi/score/query — single consented score (soft-pull) - POST /fi/decision/evaluate — policy + product-terms decision for one consented subject. Body: pairwiseSub, purpose, productCode ([A-Z0-9_], 1-40 chars; malformed => SW1001). Returns decision APPROVE|REVIEW|DECLINE, segmentKey, score/tier/confidence/reasonCodes, terms (depositRatePct, creditLimit, currency, maxTenorMonths, interestAprPct) and firedRules (ruleCode, ruleType KNOCKOUT|REVIEW, messageEn). Evaluation order: consent check + score (same path as /fi/score/query) -> segment chain {country}:{productCode} -> {country} -> GLOBAL -> KNOCKOUT rules (first match returns DECLINE immediately, no terms) -> REVIEW rules (all matches accumulate into firedRules) -> terms matrix (client-specific row beats the shared row per segment; matches on minScore <= score <= maxScore and confidence >= minConfidence with LOW < MEDIUM < HIGH) -> no matrix configured or no match => REVIEW with no terms (never an implicit approval) -> matched row's decision applies, except an APPROVE row is downgraded to REVIEW when any REVIEW rule fired; terms are omitted on DECLINE. SANDBOX: synthetic score plus the client's real configured rules and terms; a synthetic score carries no country context so only the GLOBAL segment applies. Metered as DECISION, 2 units/call. - POST /fi/score/batch — 1-100 subjects; denied subjects get the single label DENIED - POST /fi/verify — verify a presented score attestation (consent-free); reasons: VALID, NOT_FOUND, REVOKED, EXPIRED, COMMITMENT_MISMATCH, TIER_MISMATCH - GET /fi/score/history?pairwiseSub=&fromDate=&toDate= — requires 'history' consent category, max 365 days - GET /fi/whoami — authenticated client info (clientId, name, environment, rateLimitTier, status) - GET /fi/usage?fromDate=&toDate= — daily usage metering, max 92 days; endpoint buckets QUERY | BATCH | VERIFY | ISSUE | HISTORY | DECISION (unit weights: query=1, batch per subject=1, verify=1, issue=1, history=2, decision=2) - POST /fi/consent/requests — push an in-app consent request to the user - GET /fi/consent/{consentReceiptId} — consent status (ACTIVE | REVOKED | EXPIRED) - GET /fi/health — public health check Endpoints (issuer surface — institutions issuing credentials under their own name): - POST /public/issuer-onboarding/apply — public intake, NO authentication (rate limited 60 req/min per calling IP). Body: orgName (<=200), orgType GOVERNMENT|UNIVERSITY|ASSOCIATION|COMPANY, countryCode? (2 letters, stored uppercased), contactEmail (<=255, stored lowercased), requestedCredentialTypes (1-10 unique values, each <=100 chars matching [A-Za-z][A-Za-z0-9_.:-]*, JSON-encoded array <=500 chars), purposeNote? (<=1000). Returns { id } as the application receipt. Violations => SW1001. - Review is operator-side (no partner API). Application status: APPLIED (only reviewable state) | APPROVED | REJECTED (review note mandatory) | SUSPENDED. Approval creates a partner client with the credential:issue scope and returns the plaintext API key EXACTLY ONCE (stored hashed, never readable again). - Issuer DID registration is a MANUAL operator step, not part of approval: the issuer DID and public JWK must be registered in the issuer trust registry (VERIFIED trust for GOVERNMENT, PROVISIONAL for other org types) and linked to the application. Until then delegated issuance is fail-closed with SW1701 (HTTP 500) — retrying does not help; check GET /issuer/whoami. - POST /issuer/credentials/issue — issue a credential under the institution's name. Auth: X-Client-Id + X-API-Key (same headers as the FI surface). Body: holderDid (did:sw:0x..., <=255, must exist and be ACTIVE), credentialType (<=100, [A-Za-z][A-Za-z0-9_.:-]*, must be in the approved list), claims (string->string map, <=20 entries, names <=100, values <=500 and non-null), validityDays? (1-3650, default 365). The server overwrites the reserved claims credentialType and delegatedIssuer (the institution's issuer DID). Returns { credentialId }. NO idempotency key — a repeated request issues a second credential. Errors: SW2102 401 auth, SW2101 403 suspended, SW2304 403 missing credential:issue scope / unlinked or non-APPROVED application / unapproved credentialType, SW1701 500 issuer DID unregistered or untrusted, SW1402 404 holder DID not found, SW1403 403 holder DID not active, SW1002 404 no civil record for the holder DID, SW1001 400 validation, SW1004 429 rate limit. - GET /issuer/credentials?page= — credentials whose delegatedIssuer claim matches this issuer. Fixed page size 20, zero-based page; out-of-range page returns empty content, totalPages is 0 when nothing was issued. Timestamps on this surface are ISO-8601 (not the FI 'yyyy-MM-dd HH:mm:ss' format). - GET /issuer/whoami — applicationId, clientId, orgName, orgType, countryCode, contactEmail, issuerDid (null until the manual registration is done), status, credentialTypes. Callable before registration — this is how an institution checks that step. - Metering: only SUCCESSFUL issuance is counted, in the ISSUE bucket at 1 unit per call, written at request time and preserved by nightly re-aggregation. A metering failure never rolls back the issuance. Issuers read their own counts via GET /fi/usage. Docs (Markdown mirrors): - md/index.md: Overview, key numbers, plain-language glossary - md/quickstart.md: Registration, key issuance, first sandbox call, going live (ko+en) - md/auth.md: Header authentication and key management (ko+en) - md/api-reference.md: All endpoints with request/response JSON examples - md/sandbox.md: Canonical sandbox subjects (SANDBOX-TIER-A..E, SANDBOX-NO-CONSENT, SANDBOX-EXPIRED) and verify verdict cases - md/errors.md: Error codes (SW2102, SW2101, SW2300-SW2305, SW1001, SW1004), retry policy, Retry-After - md/webhooks.md: 4 event types (consent.revoked, score.updated, tier.changed, attestation.expiring), signature verification (Java/TypeScript), retries - md/consent-model.md: Deny-by-default, pairwise identifiers, data categories (score, tier, reason-codes, contributions, history, monitoring) - md/eid.md: Derived identity-verification credential from a national ID — the credential carries only the fact of verification (no ID numbers, images or source field values retained). Source status: document OCR verification (in operation), partner financial institution verification (staged rollout), Korea mobile resident registration card / Vietnam VNeID / Laos LAeID (in preparation, subject to each authority's integration schedule). Announces a planned identityAssurance block on the FI score response — NOT part of the current API; do not integrate against it yet. - md/issuer.md: Issuer onboarding — apply, operator review, one-time API key delivery, manual issuer-DID registration (fail-closed until done), delegated issuance API, ISSUE metering, troubleshooting - md/compliance.md: Implemented controls — consent, hash-chained access log, reason codes, model governance, decision provenance (facts only) - md/recipes.md: Reference underwriting patterns by tier (advisory only; lending decisions are the FI's responsibility) Machine-readable API contract: openapi.json (OpenAPI 3.0.3, FI surface plus the issuer surface)