Handling errors

Every failure is a SwarmhitError; branch on its class and its code.

When a request fails, the method throws. Every error the SDK throws extends SwarmhitError, so one catch handles them all. The error's class tells you what kind of failure it was.

import { APIError, NotFoundError, RateLimitError } from '@swarmhit/swarmhit-sdk';

try {
  const campaign = await swarmhit.campaigns.get(campaignId);
} catch (err) {
  if (err instanceof NotFoundError) {
    // the campaign does not exist (or was deleted)
  } else if (err instanceof RateLimitError) {
    console.log('limit reached', err.details);
  } else if (err instanceof APIError) {
    console.error(err.status, err.code, err.message, err.details);
  } else {
    throw err; // no answer (network, timeout), or a bug in your code
  }
}

Error classes

The API answered with an error. These extend APIError:

ClassStatusTypical cause
BadRequestError400A missing or invalid field, or a rule the request breaks (no seat left). Some missing records are a 400 with code not-found too
AuthenticationError401A missing, revoked or mistyped key, or an X-Swarmhit-Workspace the key cannot act on
PaymentRequiredError402A purchase (mailboxes, domains, seats) whose payment failed, or no card on file
PermissionDeniedError403Not allowed for this workspace or the key's creator: a feature not enabled, billing rights missing
NotFoundError404The resource does not exist in this workspace
ConflictError409The current state forbids it: a paused sender, an invitation already pending, the 25-webhook cap, an idempotency key still in use
UnprocessableEntityError422LinkedIn could not act on that profile or post
RateLimitError429A sender's daily or weekly LinkedIn limit, or LinkedIn throttling the sender
InternalServerError5xxA failure on our side (500), or LinkedIn or the mail provider failing behind the request (502, 503)

Other failures. These extend SwarmhitError but not APIError:

ClassWhen
APIConnectionErrorThe request never got an answer: network, DNS or TLS failure.
APITimeoutErrorThe timeout ran out (it extends APIConnectionError).
APIUserAbortErrorYour signal aborted the request.
WebhookVerificationErrorA webhook delivery failed verification (see Webhooks).

Before it throws a connection error, a timeout, a 5xx or a rate-limit 429, the SDK has already retried the request when that was safe.

What an APIError carries

Property
statusThe HTTP status.
codeA stable, machine-readable reason, such as seat-limit-reached, inmail_credits_exhausted or not-found. When the API sends none, a generic one for the status (invalid_request for a 400, not_found for a 404).
messageA sentence for humans. Its wording may change: don't parse it.
detailsExtra context when there is some, for example { limit, used, scope, resetAt } on a sender's limit.
headersThe response headers.

RateLimitError adds retryAfter: the number of seconds to wait, when the response carries a Retry-After header. A sender's limit says when it resets in details instead (see Sender limits).

Branch on the code

Use err.code to handle a specific situation, rather than err.message. It never changes within an API version:

async function sendInMail(senderId, profileUrl, subject, text) {
  try {
    return await swarmhit.accounts.inmails.send(senderId, { profileUrl, subject, text });
  } catch (err) {
    if (!(err instanceof APIError)) throw err;
    switch (err.code) {
      case 'inmail_credits_exhausted':
        return queueForNextMonth(profileUrl);
      case 'profile_not_found':
        return markInvalid(profileUrl);
      case 'sender_paused':
        return notifyOwner(senderId);
      default:
        throw err;
    }
  }
}

Some codes you are likely to meet:

CodeMeaning
unauthorizedThe key is missing, wrong or revoked.
not-found, not_foundNo such resource in this workspace.
invalid_requestThe generic code of a 400 without a more specific one; message says what is wrong.
seat-limit-reachedConnecting a sender needs a free seat.
profile_not_foundThe LinkedIn profile URL or identifier could not be resolved.
sender_pausedThe sender is paused; resume it first.
inmail_credits_exhaustedThe sender has no InMail credits left.
idempotency_in_progress, idempotency_failedSee Retries.

The Getting started guide describes the HTTP errors in more detail.

Sender limits

LinkedIn actions count against the sender's daily and weekly limits. Over a limit, the API answers 429. The error's details say which limit was reached ({ limit, used, scope, resetAt }, scope being day, week or pending) and when it resets:

try {
  await swarmhit.accounts.connections.send(senderId, { profileUrl });
} catch (err) {
  if (err instanceof RateLimitError && err.details?.resetAt) {
    await scheduleAt(err.details.resetAt, () => sendInvite(profileUrl));
  } else {
    throw err;
  }
}

A 429 without details comes from LinkedIn throttling the sender: back off. The SDK never retries these on its own: a retry within seconds can't help, and hurts a throttled account.

On this page