Support individual and business entity types - #355
larseidsvoll wants to merge 10 commits into
Conversation
Let a kyc provider declare which entity types it can verify (individual, business, or both) and let a verification request declare which type it is for. Both are redirect flows: the provider returns a webURL to a hosted experience and the client polls for the certificate. The entity type tells the provider which hosted experience to present. The provider hosts and owns the collection experience for both entity types (see the demo-kyc-provider app pattern: a hosted form served alongside the anchor API). So the package does not carry entity-specific input details in the request -- it only carries which kind of experience to start. This keeps the surface minimal and leaves KYB detail collection to the anchor's hosted form rather than the SDK. Changes: - common.ts: add entityType to the request (defaults to individual). webURL stays required (both flows redirect). KYCEntityType is derived from the metadata type. - server.ts: add entityTypes to the kyc config (default ['individual']) and publish it in the service metadata. - client.ts: pass entityType through to the resolver lookup. - resolver.ts: add entityTypes to the kyc service metadata, add an optional entityType filter to the kyc search criteria, and filter providers by it in lookupKYCServices (a provider with no declared entityTypes is treated as individual-only). - server.test.ts: business entity test covering metadata advertisement, entityType-filtered resolution, and a business createVerification that returns a hosted webURL. The cert schema needs no change for KYB: the existing ISO20022 attribute set (EntityType.organization, OrganizationIdentification bic/lei/other, the generic Document container) already represents business identifiers and documents. Back-compat: entityType defaults to individual and entityTypes defaults to ['individual'], so existing providers and callers are unaffected. tsc clean, make do-lint clean. (Pre-existing common.test.ts error round-trip flake fails identically on clean main -- not introduced here.)
- entityTypes metadata is now a presence map ({ individual?: true,
business?: true }) so a type cannot be declared twice, per review
- extract a named KYCEntityType type in resolver and reuse it across
the metadata, search criteria, common, and server modules
- move the business createVerification assertion into the client test
where the rest of the client flow lives; server test keeps the
metadata-publish and resolver-lookup coverage
Address review on PR #355: - entityTypes metadata is now a map of explicit booleans (mirroring supportedOperations on asset movement), and the resolver reads each key via ('boolean') and matches only when explicitly true (mirroring how supportedAffinities is read on FX). A false or missing key means unsupported, so invalid metadata cannot opt a provider into a type. - The server publishes an explicit boolean for every known entity type, defaulting to { individual: true, business: false }. - Add an entity-type combination matrix test covering individual-only, business-only, both, and undeclared providers against both requested types, so the explicit combinations are exercised, not just the implicit case. - Correct the entityTypes doc on the server config: business is a hosted redirect flow returning a webURL like individual, not a synchronous no-webURL flow.
This comment was marked as resolved.
This comment was marked as resolved.
This comment was marked as outdated.
This comment was marked as outdated.
…try codes Addresses Srayman's review on PR #355: - Build each provider/resolver once and reuse it across lookups instead of standing up a fresh server and republishing metadata on every assertion. The matrix now constructs one provider per distinct config (individual-only, business-only, both, undeclared, both over US+CA) and queries each resolver repeatedly. - Drive the assertions from a table of { provider, requested entity type, requested country codes, expected } cases looped with a single expect, with the case name passed as the assertion message so a failure names the exact combination. - Cross entity types with country codes: a supported entity type in an unsupported country is still rejected (the two filters are ANDed), and a provider declaring US+CA matches business and individual in CA. - Dispose every provider server in a finally block.
Latest push: fixes and scope trimFixesThe The status poll dropped the entity type.
Resolver edge case. Two assertions could not fail.
Scope trim
Deliberately unchanged
Verification
|
|
@cursoragent Review it! |
|
Same head as last time ( Request changes on the tests and that comment. I would not block on the hosted-redirect design, the boolean map, or the client-side default. 1. “Undeclared” in the matrix is not undeclared. 2. The country-list short-circuit is also untested. The entity filter is applied before “no 3. Split the search-criteria JSDoc. 4. The request JSDoc overclaims (nit). It says the field defaults to Still fine as follow-ups: no server-side enforcement (same as The boolean map, |
|
Co-authored-by: ezraripps <19670988+ezraripps@users.noreply.github.com>





Built in collaboration with @schenkty as part of project Gildor.
What this does
Lets a
kycprovider declare which entity types it can verify (individual,business, or both) in its service metadata, and lets a verification request declare which type it is for. This folds business verification (KYB) into the existing kyc service rather than standing up a separate service.How it works
Both individual and business are redirect flows: the provider returns a
webURLto a hosted experience and the client polls for the certificate. TheentityTypetells the provider which hosted experience to present.The provider hosts and owns the collection experience for both entity types (the demo-kyc-provider pattern: a hosted form served alongside the anchor API). So the request does not carry entity-specific input details, only which kind of experience to start. KYB detail collection lives in the anchor's hosted form, not in the SDK.
This is intentionally a minimal surface. An earlier draft carried a business-details block in the request and made
webURLoptional for a synchronous business path; that was dropped once we settled on the anchor hosting the KYB form (so business redirects too, andwebURLstays required for both).Changes
KYCEntityTypetype and thekycEntityTypesarray behind it; addentityTypesto the kyc service metadata, as a map of explicit booleans ({ individual?: boolean; business?: boolean }), mirroringsupportedOperationson asset movement; add an optionalentityTypefilter to the kyc search criteria and filter providers by it inlookupKYCServices. A type counts as supported only when its key reads back as an explicittrue, the waysupportedAffinitiesis read on FX, sofalse, a missing key, or malformed metadata all mean unsupported. A provider that declares noentityTypesat all is treated asindividual-only.entityTypesto the kyc config (defaults to['individual']). The author-facing config stays aKYCEntityType[]array and is folded into the boolean map at publish time, so the server always publishes an explicit boolean for every known entity type.entityTypeto the request (defaults toindividual).webURLstays required.KYCEntityTypecomes from the resolver, so there is one source of truth.individualdefault and pass the entity type into the resolver lookup, on both the create and the status paths.KYC.EntityType, following the existingKYC.ClientConfigconvention, so consumers do not need a deep import.Country listing is entity-aware
Resolver.listSupportedKYCCountries()takes an optionalentityType, andKYCClient.getSupportedCountries()takes one defaulting toindividual. Without this, a business-only provider's countries would be reported as supported individual KYC countries, and a provider that declares nocountryCodeswould expand the answer to every country regardless of the entity types it serves.Where the default lives
entityTypedefaults toindividualin the client's resolver lookup. The resolver itself keeps "criteria omitted means no entity filter" for low-level callers, and the HTTP request body is deliberately not normalized: an existing caller's payload is byte-for-byte what it was, so a provider running an older build with strict validation cannot start rejecting it.The cert schema needs no KYB changes
The existing KYC certificate attribute set (generated from
oids.json) is already generic and entity-agnostic, so KYB needs nothing added:EntityTypealready has anorganizationarm (SEQUENCE OF GenericOrganizationIdentification) alongsideperson.OrganizationIdentificationalready carriesbic,lei, and a genericother({ id, schemeName, issuer }).Documentis a single generic container (number, front/back/selfie references, dates, issuing authority) that every document type already reuses.Business identifiers (EIN, registration number, LEI, DUNS) map to
entityType.organization[]via ISO20022 scheme names, and KYB documents map to the genericDocument. No per-document cert fields are required.Reading that attribute back is tracked separately in #439:
entityTypeis currently skipped when decoding sharable attribute references, which is how a consumer would tell a verified business from a verified individual at the end of the flow.Not in scope
entityType. ThecreateVerificationhandler validates body shape, typia type and signature, but does not check the request's entity type against the configured one. That matchescountryCodes, which the handler never reads either: advertise in metadata, filter in the Resolver.Back-compat
entityTypedefaults toindividualandentityTypesdefaults to['individual'], so existing providers and callers are unaffected. A provider that predates the field is treated as individual-only, which means an anchor that already does KYB has to republish its metadata before it can be discovered forbusiness.Tests
server.test.ts: a both-types provider advertising and resolving on each type, an assertion that an unsupported type is published as an explicitfalserather than an absent key, and a combination matrix over individual-only, business-only, both, undeclared, and a multi-country provider, crossing entity types with country codes.client.test.ts: businesscreateVerificationthrough the client returns a hostedwebURL, the entity type reaches the provider, the status poll resolves the same provider, and a business-only provider rejects a caller thstays out of the individual country list.Verification
npx tsc --noEmit: 0 errors -make do-lint: cleanmake test: the only failures are the pre-existingcommon.test.tserror round-trip flake, which fails identically on a cleanmaincheckout. The changed files (server, client, resolver tests) all pass.Note
Medium Risk
Touches KYC provider discovery and verification request shape; defaults limit impact on existing individual-only providers and callers.
Overview
Summary
KYC providers can advertise whether they verify people or businesses, and clients can request the matching flow without a separate KYB service. Both paths stay hosted redirect: provider returns
webURL, client polls for the certificate. Omitted fields keep today’s individual-only behavior.Related Issues
None
Changes Made
individual,business) in KYC service metadata; lookup can filter onentityTypetogether with country codesentityType(defaults to individual); the KYC client passes it into resolver lookup so only capable providers are returnedentityTypesand exposes them in published metadataTesting
npx tsc --noEmit,make do-lint,make test(per author; server/client/resolver KYC tests cover business redirect and entity-type × country matrix)Breaking Changes
None
Reviewed by Cursor Bugbot for commit f401697. Configure here.