Skip to main content

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 --noEmit still resolves the SaaS DTO deep type import in lib/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.
Use this comment pattern next to any new exception:
// GHL RAW FETCH EXCEPTION (FUND-1772): <short reason SDK v3 is missing, unsafe, or disallowed here>.

Migrated in this pass

  • app/api/ghl/send-sms/route.ts and app/api/ghl/send-email/route.ts: conversations send now uses sendGHLMessage.
  • app/api/ghl/custom-fields/route.ts: custom fields now use getGHLCustomFields.
  • app/api/ghl/marketplace-charge/route.ts and lib/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 uses client.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.
const isoDay = (timeMs: number) => format(new Date(timeMs), 'yyyy-MM-dd');
  • Forms submissions must pass day-only strings in YYYY-MM-DD format, e.g. startAt: isoDay(startMs) and endAt: isoDay(endMs). Do not pass raw Date#toISOString() timestamps.
  • Transactions and orders must also pass day-only strings for startAt and endAt via isoDay(...), matching the current getAllGHLOrders call shape in app/api/admin-tracking/analytics/route.ts.
  • Calendar events must keep millisecond epoch values for startTime and endTime, matching getGHLCalendarEvents and the current admin analytics implementation. Do not convert calendar event ranges to YYYY-MM-DD day strings or ISO timestamps.

v3 breaking-change hardening

  • monetaryValue (opportunities): v3 changed the upsertOpportunity body’s monetaryValue from 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 both searchGHLOpportunities and searchGHLOpportunitiesCursor (lib/ghl-sdk.ts) and in the opportunity-value arithmetic in components/contact/ContactPageOverlays.tsx. parseNumericValue in lib/operatorInvoices.ts and the typeof === 'number' guard in app/api/ghl/crm-activity/route.ts already tolerate non-number inputs. Covered by tests/ghlMonetaryValue.test.ts.

v3 net-new field adoption

  • Opportunities lostReasonId + externalObjectId: v3 returns both on opportunity responses and accepts lostReasonId on the update body. GHLOpportunity (lib/ghl.ts) now types externalObjectId (lostReasonId was already present), so both surface through the read mappings in searchGHLOpportunitiesCursor and searchGHLOpportunities (which spread the raw opportunity). updateGHLOpportunity (lib/ghl-sdk.ts) accepts an optional lostReasonId that rides the existing as any cast into the update body (the SDK’s UpdateOpportunityDto does 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, and convex/ghlInstallations.ts must not import the SDK because it pulls Node-only dependencies.
  • Convex seed and migration utilities such as convex/seedDemoData.ts, convex/migrations/stageMapping.ts, and convex/migrations/creditReportMigration.ts are one-off or operational scripts; leave them raw unless they are moved behind Node-runtime helpers.
  • OAuth/session plumbing in lib/ghl-auth.ts and convex/lib/ghlApi.ts keeps 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.ts remains 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’s client.opportunities.searchOpportunitiesAdvanced in searchGHLOpportunities (lib/ghl-sdk.ts, re-exported from lib/ghl.ts). The SDK’s generated OpportunitySearchBodyDTO omits filters/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.1 is passed via the call’s options.headers since the SDK does not set it. Rate-limit handling uses the SDK-aware withGHLRateLimit in lib/ghl-sdk.ts (the lib/ghl-auth.ts variant only catches GHLRateLimitError, not axios 429s). Returned opportunities now apply normalizeMonetaryValue at the read source (same as searchGHLOpportunitiesCursor) so v3 object/string shapes are coerced before callers consume them.