Repository navigation
feat!: sync the SDK with blindpay-v2's public API and docs - #83
Merged
Merged
Conversation
The SDK's types were largely written by the old Claude-driven api-sync and then maintained by hand, so they drifted from the OpenAPI spec (501 audit findings). This brings them back in line and makes the drift a CI failure. - Add `| null` to the 454 fields the spec marks nullable (313 response, 141 input), via a codemod driven by the type audit's own findings. - Let the deterministic patcher insert the missing enum members it was blocked from applying (BankDetailKey, LimitIncreaseRequestSupportingDocumentType, BusinessIndustry, EstimatedTimeOfArrival), and retype six fields that were a bare string where the API guarantees a union, including a new BankAccountStatus. - sync:check now enforces nullability, and typegen emits `| null` for required fields too, so generated methods pass the gate. - Drop the dead .claude-pr/CLAUDE.md agent prompt and point CLAUDE.md at the generator before hand-writing anything. Deliberate divergences (approved_rfi, swift_payment_code, provider_name, discriminator narrowings, payin-fx narrowing) are left as recorded.
Shells only: each factory returns {} until its methods land. Wiring them
into client.ts and index.ts up front keeps the resource work in separate
files.
Two generator defects found by running the patcher against the current public spec: - --apply flagged the request/response schemas of operations it was about to generate (e.g. CreatePlaidLinkIn, AcceptTosOut) as new schemas needing a human, so it refused to apply its own output. The new-schema and enum-coverage gates now judge against the map as the run will leave it (withPendingMapEntries), which persistNewMapEntries now reuses. - singularize() turned the tos segment into to (CreatePassthroughToInput).
…atcher Payout.name/type, Transfer and CreateTransferResponse tracking_partner_fee, and CreateQuoteInput bank_account_id/request_amount are nullable in the current public spec. TrackingStatus gets an enums[] mapping so new step values (pending_refund_review) are inserted by the patcher instead of failing as needs-human on every tracking object.
…esign and analyze - instances.get() returns Instance (RetrieveInstanceOut) with InstanceType and InstanceOnboardingStep - instances.onboarding: getBusinessDetails, getBusinessProfile, getOwnershipDocuments - upload.presign (POST /v1/presign) and upload.analyze (multipart POST /v1/upload/analyze) - POST /v1/upload/extract intentionally not implemented (excluded from every SDK per blindpay-v2 docs)
Adds getDeposits, getYield, getYieldHistory, getYieldTransactions, enableYield and disableYield to blindpay.wallets.custodial, plus yield_status on CustodialWallet.
…r preapproval, CNAE and supporting-document types Adds virtualAccounts.simulateSpeiDeposit / simulateBrebDeposit (development instances only), models CustomerVirtualAccountPreapproval and CustomerCnaeCondition on the three KYC/KYB response types, adds LimitIncreaseSupportingDocument and supporting_documents to the limit-increase request/response types, and widens LimitIncreaseRequestSupportingDocumentType to the spec's 15 values.
Every blindpay-v2 route declares 'x-sdk': true|false (enforced by its check-sdk-tags.yml; apps/api/AGENTS.md: true includes it in the SDKs). loadSpec now drops x-sdk:false operations, so they and the schemas only they reach are never coverage gaps, new schemas or generator input. This excludes the yield operations, the simulated SPEI/Bre-B deposits, card payments, ToS passthrough and upload/extract. Drop the yield and simulate methods built before the flag was found; keep custodial wallet deposits, yield_status and the customer types, which x-sdk:true operations return.
…ing number Adds instances.rfi and customers.rfi (get/submit) and swift_intermediary_bank_routing_number on international SWIFT bank account types.
- Generated methods dropped parent path IDs (customers/{customer_id}/...), so
createPlaid referenced an undeclared customer_id. All path IDs after the
instance are now passed, and a lone scalar is named after its own param.
- Field inserts typed enum properties as a bare string, which the type check
then rejected, and object properties as Record<string, unknown>. They now
reuse the matching shared union (importing it), emit a literal union, and
compile object properties with the method generator's own typegen.
- insertEnumMember failed on a single-line union with no trailing semicolon
(valid in types/index.d.ts).
- detectRemovals only reports enum members a mapped SDK union still declares,
so a removal already made by hand does not block --apply.
Spec: blindpay-v2 origin/main apps/api/openapi.json through its own public filter (the last api-sync delivery was August). Snapshot refreshed by the patcher's --apply. - Map the hand-built resources (payables, RFI, onboarding, custodial wallet deposits, upload presign/analyze, customer preapproval/CNAE/supporting documents) and ignore the new *WebhookOut payloads like the existing ones. - Patcher output (575 changes): 283 enum members (BusinessIndustry's full NAICS list, Network, BankDetailKey, WebhookEvents, approved_rfi, pending_refund_review, ...), 291 fields (UK Faster Payments on bank accounts, Colombia/Mexico virtual account details, payable_id, ...) and the generated createPlaid method, plus its own changeset. - Remove the ssb banking partner and the stale WalletInboundSchemaOut ignore entry; CreateQuoteInput now only requires network and token, as the spec does. - Record the new inline nested shapes, with how each one is typed.
- api-sync field inserts now type a property that references a component ($ref, or an allOf that only annotates one): the SDK symbol it is mapped to in the same file, else the compiled component. EURB/EURC on custodial wallet balances were typed string; they are WalletTokenBalance. - Encode instance_id in the upload, analyze and presign query strings. - Fold WalletDepositNetwork into the shared Network union it now equals. - sdkSurface drops path items left with no operations; flatten the field-insert import guard; put detectRemovals' doc back on it. - Changeset: describe nullability without counts that drift; keep the ssb removal in the breaking list.
Names the inline forced_provider union PayoutForcedProvider and maps it in spec-map.json so the patcher keeps it in sync; --apply against the current public spec then added infinitus_portage.
WalletToken (USDC/USDT/USDB/EURC/EURB) replaces the deposits-only union and types transfer tokens and wallet balances; StablecoinToken stays the payin/payout set. PayinPaymentMethod is now anchored on PayinOut, the wider set, and the patcher added bre_b; payin quotes keep PaymentMethod. api-sync: a second --apply against the same spec merges into its changeset instead of overwriting the first run's entries.
The API returns every match as a bare array unless a pagination param is passed (anyOf in the spec, confirmed by the pagination docs). payouts, customers and payables now model both branches, like payins. api-sync: propsOf reads the object branches of an anyOf/oneOf body, so the payables list mapping is compared against its envelope instead of nothing.
Drops the 68 additive gaps unmodeled.json held for a reviewed PR and lets --apply insert them: payout and payin tracking sub-fields and tracking_documents, partner/transaction/billing fee amounts, customer additional_info and geolocation, instance RFI email settings, Transfer external_id, bank-account IFSC/force_cpf_cnpj, fee tiers, requiredWhen and more. Discriminators, provider-internal handles and the error envelope stay recorded. api-sync: an array field insert compiles its items (reusing a same-file symbol for a referenced schema) instead of emitting unknown[]. New named unions AdditionalInfoItem/AdditionalInfoLabel and PayoutProviderIntegration are mapped so the patcher keeps them in sync; payin tracking provider_name stays a string like its payout counterpart.
error now carries code, description, errors and trace_id next to message, for JSON requests and multipart uploads alike (toErrorResponse).
InternalApiClient's post/put/patch/delete take an optional RequestOptions whose idempotencyKey becomes the Idempotency-Key header; delete drops its never-used body. The operation generator gives every mutation the same trailing options parameter and forwards it, so generated and hand-written methods keep one signature.
…d fees with the API spec
specTypeSignature read null only from a property's own type, so a nullable $ref (PayableParty), an anyOf null branch (last_event_at) or v2's nullable allOf wrapper (GetFeesOut.ted) passed sync:check silently. It now follows those, and the 13 SDK fields that still dropped null carry it.
Every authenticated POST/PUT/PATCH/DELETE method takes a trailing options?: RequestOptions and forwards it, so options.idempotencyKey is sent as the Idempotency-Key header (the unauthenticated /e/ ToS route is left out: the API ignores it there). request-options.test.ts fails on a mutation that doesn't forward it; CLAUDE.md templates and the README show the pattern.
constructWebhookEvent and verifyWebhookSignature share verifyWithSvix and the exported WebhookVerificationInput type instead of two copies of the same call and parameter shape.
- api-sync: alternatives() stops at a $ref cycle; a DELETE with a body is left to a human (the client's delete sends none); mergedBump naming. - error: keep only the envelope's documented, well-typed fields. - Idempotency-Key: an empty key is sent (the API rejects it) instead of silently dropped. - offramp_wallets[] on bank accounts matches the API (network union, nullable external_id, circle_wallet_id). - WebhookVerificationInput sits above the client class; CLAUDE.md structure, README webhook example and changesets updated.
Contributor
✅ Snyk checks have passed. No issues have been found so far.
💻 Catch issues earlier using the plugins for VS Code, JetBrains IDEs, Visual Studio, and Eclipse. |
Contributor
🦋 Changeset detectedLatest commit: 34d5fb6 The changes in this PR will be included in the next version bump. This PR includes changesets to release 1 package
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
This brings the SDK in line with blindpay-v2's current public API and docs, and makes api-sync (the deterministic patcher) generate everything it can, so we hand-write less.
Merging produces a 6.0.0 Version Packages PR. Every breaking change is listed in the major changesets.
Coverage against blindpay-v2
x-sdk: trueoperation in the current public spec has an SDK method:--coveragereports 0 gaps and--checkreports no drift.x-sdk: falseroutes (yield, card payments, simulate,/upload/extract) are excluded bysdkSurface()in api-sync.payables,instances.rfi,customers.rfi,instances.onboarding, custodial wallet deposits, upload presign/analyze,bankAccounts.createFasterPayments..api-sync/unmodeled.jsonas "needs a reviewed PR" are now modeled by--apply. That covers:tracking_documentsadditional_infoand geolocationTransfer.external_idsync:check. It now also seesnullexpressed through$ref,anyOf,oneOfandallOf.Fixes from the docs sweep (spec-confirmed only)
bankAccounts.get,VirtualAccount.us,GetFeesResponse, partner fees (phantom wallet fields removed), payablefrom/to, customer owners on create and update,lightKYC customers,BusinessIndustry, and payout create inputs (wallet_idpath).{ success: true }bodies are typed asSuccessResponseinstead ofvoid.instance_idto the client's instance.New capabilities
options?: RequestOptions. For example,blindpay.payouts.createEvm(input, { idempotencyKey })sends theIdempotency-Keyheader. The api-sync generator emits the same signature, andrequest-options.test.tsfails if a mutation doesn't forward it.errornow carriescode,description,errorsandtrace_idnext tomessage.blindpay.constructWebhookEvent(...)verifies with Svix and returns aWebhookEventunion discriminated onwebhook_event.WalletTokenadds EURC and EURB to transfers and wallet balances. Payins reportbre_b.api-sync (the deterministic patcher)
x-sdkflag.$ref/allOf, arrays and enums.Test plan
bun run check-types,lint:check,test(281),sync:check,contract-check,build: all exit 0bun scripts/api-sync/index.ts --apply --spec <current v2 public spec>applies 0 changes--apply, type-check, and are idempotentFor humans (docs-only, not encoded in the SDK)
/payouts/evmor/payouts/solana.offramp-wallets.mdsays there's no list endpoint, but the spec has one.pq_, spec saysqu_cover_feeson transfer quotes: required in the docs, optional in the specbilling_fee_amountpayin.partnerFeeandpayout.partnerFee.CreateCustomerIn, not the per-kyc_typerequired fields.