GHL SDK v3 Raw Fetch Audit
FUND-1772 migrates high-confidence GHL traffic to@gohighlevel/api-client@3.0.0 wrappers and documents the raw calls that remain intentional.
SDK upgrade checklist
When bumping@gohighlevel/api-client, verify:
bunx tsc --noEmitstill resolves the SaaS DTO deep type import inlib/ghl-sdk.ts(@gohighlevel/api-client/dist/lib/code/saas-api/models/saas-api). The SDK does not re-export these types from its package root; a build-tool or output-directory change can break the path.
Migrated in this pass
app/api/ghl/send-sms/route.tsandapp/api/ghl/send-email/route.ts: conversations send now usessendGHLMessage.app/api/ghl/custom-fields/route.ts: custom fields now usegetGHLCustomFields.app/api/ghl/marketplace-charge/route.tsandlib/billing.ts: marketplace charge and has-funds calls now use SDK wrappers.lib/ghl.ts: products and product prices now use SDK wrappers while preserving exact-name matching.lib/ghl-location-resources.ts: workflow and pipeline lookups for existing prospecting locations now use SDK-backed helpers.lib/ghl-sdk.ts: contact upsert now usesclient.contacts.upsertContact.
Date-shape requirements for follow-up migrations
Keep these shapes explicit in any PR 2 migration snippet that moves form, payment, or calendar reads to the SDK. GHL accepts multiple date-looking values but applies date-range filtering differently by endpoint.- Forms submissions must pass day-only strings in
YYYY-MM-DDformat, e.g.startAt: isoDay(startMs)andendAt: isoDay(endMs). Do not pass rawDate#toISOString()timestamps. - Transactions and orders must also pass day-only strings for
startAtandendAtviaisoDay(...), matching the currentgetAllGHLOrderscall shape inapp/api/admin-tracking/analytics/route.ts. - Calendar events must keep millisecond epoch values for
startTimeandendTime, matchinggetGHLCalendarEventsand the current admin analytics implementation. Do not convert calendar event ranges toYYYY-MM-DDday strings or ISO timestamps.
v3 breaking-change hardening
monetaryValue(opportunities): v3 changed theupsertOpportunitybody’smonetaryValuefrom a number to an object, and read responses are not guaranteed to stay a primitive number across endpoints.lib/ghl-monetary.ts(normalizeMonetaryValue/monetaryValueOrZero) coerces number | numeric-string |{ amount }-style object to a finite number. It is applied at the read source in bothsearchGHLOpportunitiesandsearchGHLOpportunitiesCursor(lib/ghl-sdk.ts) and in the opportunity-value arithmetic incomponents/contact/ContactPageOverlays.tsx.parseNumericValueinlib/operatorInvoices.tsand thetypeof === 'number'guard inapp/api/ghl/crm-activity/route.tsalready tolerate non-number inputs. Covered bytests/ghlMonetaryValue.test.ts.
v3 net-new field adoption
- Opportunities
lostReasonId+externalObjectId: v3 returns both on opportunity responses and acceptslostReasonIdon the update body.GHLOpportunity(lib/ghl.ts) now typesexternalObjectId(lostReasonIdwas already present), so both surface through the read mappings insearchGHLOpportunitiesCursorandsearchGHLOpportunities(which spread the raw opportunity).updateGHLOpportunity(lib/ghl-sdk.ts) accepts an optionallostReasonIdthat rides the existingas anycast into the update body (the SDK’sUpdateOpportunityDtodoes not yet type it).
Intentional raw exceptions
- Convex V8 runtime files such as
convex/lib/ghlApi.ts,convex/underwriting.ts,convex/creditReports.ts,convex/leadNotifications.ts,convex/prospecting/reportsEmail.ts, andconvex/ghlInstallations.tsmust not import the SDK because it pulls Node-only dependencies. - Convex seed and migration utilities such as
convex/seedDemoData.ts,convex/migrations/stageMapping.ts, andconvex/migrations/creditReportMigration.tsare one-off or operational scripts; leave them raw unless they are moved behind Node-runtime helpers. - OAuth/session plumbing in
lib/ghl-auth.tsandconvex/lib/ghlApi.tskeeps raw token calls to preserve the app’s existing refresh, persistence, and location-token exchange behavior. - Legacy migration/import tooling such as
lib/zoho-migration/loader.tsremains raw because it is not new app traffic and has custom reconciliation behavior. - Specialized endpoints without safe SDK coverage remain raw, including live-chat typing, call recording upload/attachment, and selected chat/webhook fallbacks.
Retired exceptions
- Advanced opportunity POST search (
POST /opportunities/search): now routed through the SDK’sclient.opportunities.searchOpportunitiesAdvancedinsearchGHLOpportunities(lib/ghl-sdk.ts, re-exported fromlib/ghl.ts). The SDK’s generatedOpportunitySearchBodyDTOomitsfilters/sort/page, so the hand-built body is forwarded via a cast (the SDK passes the body to axios verbatim).User-Agent: curl/8.7.1is passed via the call’soptions.headerssince the SDK does not set it. Rate-limit handling uses the SDK-awarewithGHLRateLimitinlib/ghl-sdk.ts(thelib/ghl-auth.tsvariant only catchesGHLRateLimitError, not axios 429s). Returned opportunities now applynormalizeMonetaryValueat the read source (same assearchGHLOpportunitiesCursor) so v3 object/string shapes are coerced before callers consume them.

