From ec644070bc171bf9089222d9d6d04a4bf8c90b4b Mon Sep 17 00:00:00 2001 From: Akhilesh Agarwal Date: Wed, 6 May 2026 04:34:02 -0400 Subject: [PATCH] fix: send ESG country/domain as query params (matches [FromQuery] binding) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Cross-repo audit of all 47 SDK methods against the live QubitOn API surfaced one issue: lookupESGScore was sending country and domain in the JSON body, but the server binds them as [FromQuery] on ESGController -- they were silently dropped, causing scoring to default to global / no-domain results. Same drift surface as qubitonhq/qubiton-go#1, qubitonhq/qubiton-sap#2, qubitonhq/qubiton-oracle#2. Fix --- Strip country and domain off the request body and serialise them into the URL query string with URLSearchParams (handles percent-encoding). The body now contains only companyName, esgId, and BaseRequest fields. Existing call sites that don't set country/domain see no behaviour change. Call sites that DO set them (intending to filter by region or domain) now correctly produce ?country=…&domain=… on the URL. EsgScoresRequest's country and domain JSDoc was updated to flag they are sent as query parameters, not body. Audit findings outside this PR ------------------------------- 46 of 47 SDK methods are correctly aligned with the API. Spot-checked: - TaxValidateRequest: identityNumber/identityNumberType/country/entityName ✅ - TaxFormatValidateRequest: identityNumber/identityNumberType/countryIso2 ✅ - BusinessRegistrationRequest: entityName ✅ - EntityRiskRequest.CountryOfIncorporation: PascalCase, matches the pinned [JsonPropertyName("CountryOfIncorporation")] override on the server DTO that escapes the camelCase naming policy. ✅ The Node SDK maintainer had already documented this trap in the model comment ("the lowercase countryOfIncorporation alias is NOT accepted by the server"). - BeneficialOwnershipRequest: countryIso2 ✅ Tests ----- tsc --noEmit clean. Full test suite passes (22/22 tests). --- src/client.ts | 11 ++++++++++- src/models.ts | 9 +++++++++ 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/src/client.ts b/src/client.ts index 2bb613a..a72d125 100644 --- a/src/client.ts +++ b/src/client.ts @@ -633,7 +633,16 @@ export class QubitOnClient { /** ESG scores. Returns a JSON array of `EsgScoresResponseEntry`. */ async lookupESGScore(req: EsgScoresRequest, options?: RequestOptions): Promise { - return this.post('/api/esg/Scores', req, options); + // `country` and `domain` are bound on the server as [FromQuery] (not body). + // Strip them out of the body and serialise into the URL with safe encoding. + // The body keeps only companyName, esgId, and BaseRequest fields. + const { country, domain, ...body } = req; + const params = new URLSearchParams(); + if (country) params.set('country', country); + if (domain) params.set('domain', domain); + const qs = params.toString(); + const path = qs ? `/api/esg/Scores?${qs}` : '/api/esg/Scores'; + return this.post(path, body, options); } async domainSecurityReport( diff --git a/src/models.ts b/src/models.ts index 719bad5..159dcc3 100644 --- a/src/models.ts +++ b/src/models.ts @@ -1103,9 +1103,18 @@ export interface CreditAnalysisResponse extends BaseResponse { // ── ESG Score ───────────────────────────────────────────────────────────── +/** + * Request shape for `lookupESGScore`. The body sent on the wire contains only + * `companyName` and `esgId` (plus `BaseRequest` fields). `country` and `domain` + * are bound on the server as `[FromQuery]` parameters on `ESGController` — + * the SDK strips them out of the body and serialises them into the URL + * query string. Sending them in the body would silently no-op. + */ export interface EsgScoresRequest extends BaseRequest { companyName: string; + /** Sent as URL query parameter `?country=…`, NOT body. ISO 3166-1 alpha-2/3 or full name. */ country?: string; + /** Sent as URL query parameter `&domain=…`, NOT body. e.g. "example.com". */ domain?: string; esgId?: string; }