Profiles·Public

@hubspot/api-client

semver>=1.0.0 <14.0.0postconditions19functions14last verified2026-06-24coverage score93%

Postconditions: what we check

  • create · create-no-try-catch
    error
    Whencreate() called in async context without surrounding try/catch. HubSpot API errors (rate limiting, invalid data, auth failures) result in an unhandled rejected Promise that crashes the request.
    ThrowsRequestError (HTTP 4xx/5xx) — thrown on non-2xx response. status 401: Invalid/missing API key. status 403: Insufficient OAuth scope. status 409: Duplicate object (contact with email already exists). status 429: Rate limit exceeded. Error: Network failure (DNS, timeout).
    Required handlingCaller MUST wrap create() in try/catch. Handle at minimum: - 409 Conflict for duplicate objects (extract existing ID from error message) - 429 Too Many Requests for rate limiting (implement backoff) - Generic Error for network failures Do NOT let create() failures crash the request pipeline.
    costmediumin prodimmediate exceptionusers seeservice unavailablevisibilityvisible
    Sources[1][2]
  • update · update-no-try-catch
    error
    Whenupdate() called in async context without surrounding try/catch. Object-not-found errors and API failures result in unhandled rejections.
    ThrowsRequestError (HTTP 4xx/5xx). status 404: Object not found with given ID. status 401/403: Authentication/authorization failure. status 429: Rate limit exceeded.
    Required handlingCaller MUST wrap update() in try/catch. Handle 404 as graceful degradation — object may have been deleted. Handle 429 with exponential backoff.
    costmediumin prodimmediate exceptionusers seeservice unavailablevisibilityvisible
    Sources[3][2]
  • archive · archive-no-try-catch
    error
    Whenarchive() called in async context without surrounding try/catch. Throws if object not found or auth fails.
    ThrowsRequestError (HTTP 4xx/5xx). status 404: Object not found. status 401/403: Authentication failure.
    Required handlingCaller MUST wrap archive() in try/catch. Handle 404 gracefully — object may already be archived/deleted.
    costmediumin prodimmediate exceptionusers seeservice unavailablevisibilityvisible
    Sources[4][2]
  • doSearch · dosearch-no-try-catch
    error
    WhendoSearch() called in async context without surrounding try/catch. API errors result in unhandled rejections.
    ThrowsRequestError (HTTP 4xx/5xx). status 400: Invalid search request (bad filter syntax). status 401/403: Authentication failure. status 429: Rate limit exceeded.
    Required handlingCaller MUST wrap doSearch() in try/catch. Return empty results on failure rather than crashing.
    costmediumin prodimmediate exceptionusers seeservice unavailablevisibilityvisible
    Sources[5][2]
  • getById · getbyid-no-try-catch
    error
    WhengetById() called in async context without surrounding try/catch. Object-not-found and auth errors result in unhandled rejections.
    ThrowsRequestError (HTTP 4xx/5xx). status 404: Object not found with given ID. status 401/403: Authentication failure.
    Required handlingCaller MUST wrap getById() in try/catch. Handle 404 as graceful not-found — return null instead of throwing.
    costmediumin prodimmediate exceptionusers seeservice unavailablevisibilityvisible
    Sources[6][2]
  • upsert · upsert-partial-failure-unchecked
    error
    WhenbatchApi.upsert() returns HTTP 207 when some records fail (e.g., validation errors, duplicate email with email as idProperty). The SDK returns a BatchResponseSimplePublicObjectWithErrors object — it does NOT throw. Callers who do not inspect response.errors[] lose visibility into which records failed silently.
    ThrowsDoes NOT throw on partial failure. Returns BatchResponseSimplePublicObjectWithErrors with HTTP 207. response.errors[] contains per-record failure details. DOES throw ApiException<ErrorBody> on total failure (401, 403, 429, 5xx).
    Required handlingCaller MUST check the return value status code or response.errors array: - If response.errors exists and has items, handle each failed record individually - Log or re-queue failed records — they were NOT upserted - On total failure (thrown ApiException), wrap in try/catch Note: partial upserts are NOT supported when using email as idProperty — any email duplicate causes the ENTIRE batch to fail with 400.
    costhighin prodsilent failureusers seelost datavisibilitysilent
    Sources[2][4]
  • upsert · upsert-no-try-catch
    error
    WhenbatchApi.upsert() called in async context without surrounding try/catch. Authentication failures, rate limits, and server errors throw ApiException.
    ThrowsApiException<ErrorBody> (extends Error) with code (HTTP status), body, headers. status 401: Invalid/missing access token. status 403: Insufficient OAuth scope for batch write operations. status 423: HubSpot account locked (high-volume upsert triggered lock, persists 2s). status 429: Rate limit exceeded. status 500/504: HubSpot server error or timeout.
    Required handlingCaller MUST wrap batchApi.upsert() in try/catch. Check error.code for HTTP status. Implement exponential backoff for 429 (rate limit) and 423 (lock). Do NOT retry 401/403 without fixing credentials.
    costmediumin prodimmediate exceptionusers seeservice unavailablevisibilityvisible
    Sources[2]
  • merge · merge-no-try-catch
    error
    WhenbasicApi.merge() called in async context without surrounding try/catch. Throws ApiException for auth failures, non-existent IDs, and merge limits.
    ThrowsApiException<ErrorBody> (extends Error) with code (HTTP status). status 404: Either primaryObjectId or objectIdToMerge does not exist. status 401/403: Authentication or insufficient scope. status 414: Combined merge history of both records exceeds 250 entries (URI too long). This is a HubSpot-specific hard limit — cannot be worked around. status 429: Rate limit exceeded.
    Required handlingCaller MUST wrap merge() in try/catch. Handle 404 gracefully — one or both records may have already been merged or deleted. Handle 414 by checking merge history counts before calling merge — records with 250+ total prior merges cannot be merged further via API.
    costmediumin prodimmediate exceptionusers seeservice unavailablevisibilityvisible
    Sources[2]
  • purge · purge-no-try-catch
    error
    WhenbasicApi.purge() called in async context without surrounding try/catch. GDPR delete operations throw ApiException on auth errors and missing records. Errors during GDPR compliance workflows have legal implications.
    ThrowsApiException<ErrorBody> (extends Error) with code (HTTP status). status 401: Missing or invalid access token. status 403: Access token lacks 'crm.objects.contacts.sensitive.read' scope required for GDPR delete operations. status 404: Contact not found by email or objectId. status 429: Rate limit exceeded.
    Required handlingCaller MUST wrap purge() in try/catch. Log all errors — failed GDPR delete attempts are a compliance risk. Handle 403 by checking OAuth scopes — the GDPR delete scope is NOT included in standard CRM write scopes and must be explicitly requested. Handle 404 as "already deleted" — verify the contact is gone before reporting success. DO NOT silently swallow errors — GDPR deletion failures require operator notification.
    costhighin prodimmediate exceptionusers seeservice unavailablevisibilityvisible
    Sources[4][2]
  • getAll · getall-no-try-catch
    error
    Whencrm.<object>.getAll() called in async context without surrounding try/catch. Inherits all getPage() error contracts. Network failures partway through pagination throw ApiException after partially consuming results — caller gets no data.
    ThrowsApiException<ErrorBody> (extends Error) with code (HTTP status). status 401/403: Authentication failure throws immediately on first page. status 429: Rate limit exceeded — throws mid-pagination if rate limit hit. status 500/504: HubSpot server error during any page fetch.
    Required handlingCaller MUST wrap getAll() in try/catch. Consider using getPage() with manual pagination instead of getAll() for large datasets — getAll() loads ALL records into memory with no limit. Do NOT call getAll() on contact/company/deal CRM objects without estimating record count first — portals with 100k+ records will cause OOM crashes.
    costmediumin prodimmediate exceptionusers seeservice unavailablevisibilityvisible
    Sources[2]
  • batchCreate · batch-create-partial-failure-unchecked
    error
    WhenbatchApi.create() returns HTTP 207 when some records in the batch fail (e.g., duplicate email, invalid property values, missing required fields). The SDK returns a BatchResponseSimplePublicObjectWithErrors object — it does NOT throw. Callers who do not inspect response.errors[] silently lose failed records.
    ThrowsDoes NOT throw on partial failure (HTTP 207). Returns BatchResponseSimplePublicObjectWithErrors with response.errors[] containing per-record failures and response.results[] containing successful records. DOES throw ApiException<ErrorBody> on total failure (401, 403, 409, 429, 5xx).
    Required handlingCaller MUST check response.errors or response.numErrors after each batch create call. If response.errors exists and has items, log each failed record with its error message. Failed records must be retried individually or flagged for operator review. HTTP 207 does NOT throw — callers relying on try/catch alone will silently lose data. Wrap in try/catch for total failures (auth errors, rate limits, server errors).
    costhighin prodsilent failureusers seelost datavisibilitysilent
    Sources[2][4]
  • batchCreate · batch-create-no-try-catch
    error
    WhenbatchApi.create() called in async context without surrounding try/catch. Total failures (auth, rate limits, server errors) throw ApiException.
    ThrowsApiException<ErrorBody> (extends Error) with code (HTTP status). status 401: Invalid/missing access token. status 403: Insufficient OAuth scope for CRM write operations. status 409: All records in batch have duplicate values (total failure). status 423: HubSpot account locked due to concurrent high-volume operations (retry after 2s). status 429: Rate limit exceeded. status 500/504: HubSpot server error or timeout.
    Required handlingCaller MUST wrap batchApi.create() in try/catch for total failures. Implement exponential backoff for 429 (rate limit) and 423 (account lock). Do NOT retry 401/403 without rotating credentials/scopes.
    costmediumin prodimmediate exceptionusers seeservice unavailablevisibilityvisible
    Sources[2]
  • batchUpdate · batch-update-partial-failure-unchecked
    error
    WhenbatchApi.update() returns HTTP 207 when some records fail (e.g., record IDs that no longer exist, validation errors on specific properties). The SDK returns BatchResponseSimplePublicObjectWithErrors — it does NOT throw. Callers who skip checking response.errors[] lose visibility into which updates were applied.
    ThrowsDoes NOT throw on partial failure (HTTP 207). Returns BatchResponseSimplePublicObjectWithErrors with response.errors[] per-record. DOES throw ApiException<ErrorBody> on total failure (401, 403, 429, 5xx).
    Required handlingCaller MUST check response.errors or response.numErrors after each batch update call. Failed records are not retried by the SDK — caller is responsible for re-queuing. Wrap in try/catch for total failures.
    costhighin prodsilent failureusers seelost datavisibilitysilent
    Sources[2][4]
  • batchUpdate · batch-update-no-try-catch
    error
    WhenbatchApi.update() called in async context without surrounding try/catch. Total failures throw ApiException.
    ThrowsApiException<ErrorBody> (extends Error) with code (HTTP status). status 401/403: Authentication or scope failure. status 429: Rate limit exceeded. status 500/504: Server error.
    Required handlingCaller MUST wrap batchApi.update() in try/catch. Implement backoff for 429. Log all partial failures from response.errors[].
    costmediumin prodimmediate exceptionusers seeservice unavailablevisibilityvisible
    Sources[2]
  • timeline.eventsApi.create · timeline-create-wrong-auth-type
    error
    Whenclient.crm.timeline.eventsApi.create() called with a client configured using a developer API key or private app access token instead of an OAuth 2.0 access token. HubSpot explicitly rejects non-OAuth tokens for timeline event creation.
    ThrowsApiException<ErrorBody> with HTTP 403 when called with developer API key or private app token. The error body contains a message indicating the authentication method is not supported for this endpoint.
    Required handlingTimeline events MUST use OAuth 2.0 access tokens only. Configure the HubSpot Client with { accessToken: oauthToken } (not apiKey or developerApiKey). Callers using a single-auth client for all HubSpot operations must create a separate client instance for timeline operations.
    costmediumin prodimmediate exceptionusers seeservice unavailablevisibilityvisible
    Sources[7][2]
  • timeline.eventsApi.create · timeline-create-no-try-catch
    error
    Whenclient.crm.timeline.eventsApi.create() called in async context without surrounding try/catch. ApiException thrown on non-201 responses including invalid template IDs, missing required tokens, and object-not-found for the target CRM record.
    ThrowsApiException<ErrorBody> (extends Error) with code (HTTP status). status 400: Missing required event tokens, invalid eventTemplateId, or event body exceeds 1MB total / 510KB per token size limit. status 401: Invalid or missing OAuth access token. status 403: Access token lacks required OAuth scope, OR developer API key / private app token was used instead of OAuth (explicitly not supported). status 404: eventTemplateId does not exist or target objectId (company/deal/ticket) not found. status 429: Rate limit exceeded.
    Required handlingCaller MUST wrap eventsApi.create() in try/catch. Handle 400 by validating eventTemplateId and all required token values before calling. Handle 404 by checking that the target CRM record exists. Note: for contacts, if email is used and contact does not exist, a new contact is automatically created — this is distinct from companies/deals/tickets where 404 is thrown.
    costmediumin prodimmediate exceptionusers seeservice unavailablevisibilityvisible
    Sources[7][2]
  • oauth.tokensApi.create · oauth-tokens-create-no-try-catch
    error
    Whenclient.oauth.tokensApi.create() called in async context without surrounding try/catch. OAuth authorization codes are single-use and expire within minutes. Expired, already-used, or redirectUri-mismatched codes throw ApiException.
    ThrowsApiException<ErrorBody> (extends Error) with code (HTTP status). status 400: invalid_grant — authorization code expired (codes expire quickly after user grants access), already exchanged, or redirectUri does not match the value used in the authorization URL. status 400: invalid_client — clientId or clientSecret is invalid. status 400: invalid_request — missing required parameters (code, redirectUri, etc.). status 401: Unauthorized — credentials rejected by HubSpot. Error body conforms to RFC 6749: { error: "invalid_grant", error_description: "...", status: "BAD_REFRESH_TOKEN" } for refresh token failures.
    Required handlingCaller MUST wrap tokensApi.create() in try/catch. Authorization codes expire within minutes — implement the OAuth callback handler to exchange the code immediately; do not queue it for later processing. On invalid_grant, the user must restart the OAuth flow — do not retry with the same code. Store returned access_token and refresh_token securely; handle the refresh flow (grantType='refresh_token') with the same try/catch pattern when the access token expires.
    costhighin prodimmediate exceptionusers seeauthentication failurevisibilityvisible
    Sources[8][2]
  • apiRequest · api-request-no-try-catch
    error
    Whenclient.apiRequest() called in async context without surrounding try/catch. The underlying node-fetch call rejects on network failures (DNS resolution, ECONNRESET, socket timeout, AbortSignal). When numberOfApiCallRetries is configured, the RetryDecorator may eat 5xx but still rejects on transport errors after retries are exhausted.
    ThrowsFetchError (from node-fetch) on network/transport failures: type 'system' with code ENOTFOUND (DNS resolution failure) type 'system' with code ECONNRESET (connection reset by peer) type 'system' with code ECONNREFUSED (HubSpot endpoint unreachable) type 'request-timeout' (default 30s socket timeout exceeded) AbortError when an external AbortSignal is triggered Note: HTTP 4xx/5xx do NOT throw — see api-request-response-status-unchecked.
    Required handlingCaller MUST wrap apiRequest() in try/catch to catch FetchError network failures. On transport failure, decide whether the call is safe to retry (idempotent GET/PUT/DELETE) or must be surfaced as a hard failure (non-idempotent POST). Do NOT use apiRequest() inside a fire-and-forget pattern without error capture — unhandled rejections crash the Node process under default unhandled-rejection policy (>= Node 15).
    costmediumin prodimmediate exceptionusers seeservice unavailablevisibilityvisible
    Sources[9][10][2]
  • apiRequest · api-request-response-status-unchecked
    error
    Whenclient.apiRequest() return value used without inspecting response.ok or response.status. Unlike every other Client.* method in this SDK, apiRequest() does NOT throw on HTTP 4xx/5xx — the codegen apis call isCodeInRange and throw ApiException, but HttpClient.send() (used by apiRequest) returns the raw node-fetch Response untouched. Calling response.json() on an error response parses the HubSpot error body as if it were a success body, silently corrupting downstream logic.
    ThrowsNO exception is thrown on HTTP errors. Caller silently processes an error body (e.g. { status: 'error', message: 'Authentication credentials not found', correlationId: '...' }) as if it were the wrapped endpoint's success shape. Common silent failure modes: 401 Unauthorized — token missing or expired, response.json() returns the HubSpot auth error envelope, not the expected resource. 403 Forbidden — access token lacks the OAuth scope for /crm/v3/objects/*. 404 Not Found — wrong path or object id, returns HubSpot 404 body. 429 Too Many Requests — rate limit hit, no retry attempted because RetryDecorator only fires on thrown error.code. 5xx Server Error — HubSpot incident, silently treated as success body.
    Required handlingCaller MUST explicitly check response.ok (or equivalently response.status) BEFORE parsing the body: const response = await client.apiRequest({ ... }); if (!response.ok) { const errorBody = await response.json().catch(() => ({})); throw new Error(`HubSpot request failed: ${response.status} ${response.statusText} — ${errorBody?.message ?? ''}`); } const data = await response.json(); Do NOT call response.json() / response.text() before the .ok check — parsing the error body as if it were the success shape is the silent-failure path this postcondition exists to catch. RetryDecorator does NOT help here because it watches for thrown error.code; apiRequest() never throws on HTTP.
    costhighin prodsilent failureusers seelost datavisibilitysilent
    Sources[9][11][2]

Sources

Every postcondition cites at least one of these. Grouped by source type; numbered to match the footnotes above.

Official documentation
  • [1]
    developers.hubspot.com/docs/api/crm
    Contacts
  • [2]
    developers.hubspot.com/docs/api/error-handling
    Error Handling
  • [3]
    developers.hubspot.com/docs/api/crm
    Contacts
  • [4]
    developers.hubspot.com/docs/api/crm
    Contacts
  • [5]
    developers.hubspot.com/docs/api/crm
    Search
  • [6]
    developers.hubspot.com/docs/api/crm
    Contacts
  • [7]
    developers.hubspot.com/docs/api/timeline
    Timeline
  • [8]
    developers.hubspot.com/docs/api/oauth
    Tokens
  • [11]
    developer.mozilla.org/en-US/docs/Web
    Ok
Source code

Research notes

Curator notes from SOURCES.md captured when the profile was written so you can verify the reasoning, not just the rules.

Sources: @hubspot/api-client

Official Documentation

Error Contract Evidence

From the official error handling docs:

  • All API methods throw when the response status is 4xx or 5xx
  • 401: Invalid API key or expired OAuth token
  • 403: Insufficient OAuth scope
  • 404: Object not found by ID
  • 409: Conflict (e.g., contact with email already exists) — error message contains existing ID
  • 429: Rate limit exceeded — requires exponential backoff
  • 5xx: Server-side HubSpot error — retry with backoff

From SDK source (github.com/HubSpot/hubspot-api-nodejs):

  • The SDK wraps node-fetch / axios internally
  • Non-2xx responses throw an Error with body.message and statusCode
  • Network errors propagate as native Error objects

Real-World Evidence

cal.com (packages/app-store/hubspot/lib/CrmService.ts)

  • Version: 6.0.1
  • Direct violations found:
    • hubspotClient.crm.objects.meetings.basicApi.create() — no try-catch (TP)
    • hubspotClient.crm.associations.batchApi.create() — no try-catch (TP)
    • hubspotClient.crm.objects.meetings.basicApi.update() (×2) — no try-catch (TP)
    • hubspotClient.crm.objects.meetings.basicApi.archive() — no try-catch (TP)
    • hubspotClient.crm.contacts.searchApi.doSearch() — no try-catch (TP)
    • hubspotClient.crm.contacts.basicApi.getById() — no try-catch (TP)
  • Properly handled (negative examples):
    • hubspotClient.crm.properties.coreApi.getAll() — wrapped in try-catch ✅
    • hubspotClient.crm.contacts.basicApi.create() — has .catch() ✅
    • hubspotClient.crm.contacts.basicApi.update() — wrapped in try-catch ✅
    • hubspotClient.crm.owners.ownersApi.getPage() — wrapped in try-catch ✅
Need a different package?
Request a profile