Errors

The typed MedblocksError hierarchy with instanceof discrimination.

Every non-2xx response from the Medblocks API is parsed into a typed subclass of MedblocksError. Discriminate at the catch site with instanceof and log error.requestId in every branch.

Hierarchy

MedblocksError                    base - every API error extends this
├── MedblocksAuthenticationError  401 - missing / invalid / expired key
├── MedblocksPermissionError      403 - authenticated but not allowed
├── MedblocksInvalidRequestError  400 - validation failure (see error.param)
├── MedblocksNotFoundError        404 - resource not in your org
├── MedblocksConflictError        409 - duplicate id, state-machine violation
├── MedblocksRateLimitError       429 - quota exceeded (see error.retryAfter)
├── MedblocksEhrError             502 - upstream EHR failed
└── MedblocksApiError             500 - unexpected Medblocks-side error

MedblocksSignatureError is a separate class for webhook signature verification. It never carries an HTTP status.

Fields On Every Error

FieldTypeSource
typestringerror.type from the envelope
codestringerror.code. Stable, machine-readable
messagestringerror.message. Human readable
paramstring | nullerror.param. The offending field, when known
docUrlstringerror.doc_url. Link to the relevant docs page
requestIdstringerror.request_id or the X-Request-Id response header
statusCodenumberHTTP status

MedblocksRateLimitError additionally carries retryAfter: number | null parsed from the Retry-After header (seconds, or null if unparseable).

Discriminating With instanceof

import {
  MedblocksAuthenticationError,
  MedblocksConflictError,
  MedblocksInvalidRequestError,
  MedblocksNotFoundError,
  MedblocksRateLimitError,
  MedblocksError,
} from "@medblocks/connect";

try {
  await mb.patientFlow.init(input);
} catch (err) {
  if (err instanceof MedblocksAuthenticationError) {
    // 401 - surface a "check your API key" message in your alerting
  } else if (err instanceof MedblocksInvalidRequestError) {
    // 400 - err.param tells you which field
    console.error("invalid", { param: err.param, code: err.code, requestId: err.requestId });
  } else if (err instanceof MedblocksConflictError) {
    // 409 - e.g. external_id_already_exists
  } else if (err instanceof MedblocksNotFoundError) {
    // 404
  } else if (err instanceof MedblocksRateLimitError) {
    // 429 - wait err.retryAfter seconds
  } else if (err instanceof MedblocksError) {
    // anything else - log it
  } else {
    throw err;
  }
}

The base MedblocksError is also a catch-all for any future error type the API adds. New types are returned as base MedblocksError instances rather than as a TypeScript miss.

Common Codes By Subclass

SubclassCodes you’ll see
MedblocksAuthenticationErrormissing_api_key, invalid_api_key, expired_api_key, not_authenticated
MedblocksPermissionErrorforbidden, insufficient_scope, org_access_denied, role_insufficient
MedblocksInvalidRequestErrorbad_request, invalid_data, unsupported_api_version, payload_too_large
MedblocksNotFoundErrorresource_not_found
MedblocksConflictErrorexternal_id_already_exists, resource_conflict, webhook_endpoint_limit_reached
MedblocksRateLimitErrorthrottled, quota_exceeded
MedblocksEhrErroroauth_error, token_exchange_failed, token_unavailable, fhir_error, storage_error, email_error
MedblocksApiErrorinternal_error, db_error, config_missing, unexpected_response, invalid_response

The full list is on API Errors.

Webhook Signature Errors

MedblocksSignatureError is thrown by Medblocks.webhooks.constructEvent when an incoming delivery fails verification. It carries reason, not a status code:

reasonCause
missing_headerNo Medblocks-Signature header on the request.
malformed_headerHeader did not match t=<sec>,v1=<hex>.
timestamp_expiredt outside the tolerance window (default 5 min).
signature_mismatchHMAC mismatch. Wrong secret or tampered body.
malformed_bodyBody was not valid JSON in the event envelope shape.
import { MedblocksSignatureError } from "@medblocks/connect";

try {
  const event = await Medblocks.webhooks.constructEvent(rawBody, sigHeader, secret);
  // ...
} catch (err) {
  if (err instanceof MedblocksSignatureError) {
    console.warn("signature failed", err.reason);
    return new Response("bad signature", { status: 400 });
  }
  throw err;
}

Logging

requestId is the single most useful field in a support ticket. Always log it on the error path:

catch (err) {
  if (err instanceof MedblocksError) {
    logger.error("medblocks api error", {
      type: err.type,
      code: err.code,
      requestId: err.requestId,
      statusCode: err.statusCode,
      param: err.param,
    });
  }
  throw err;
}

Retries

The SDK retries transient network errors and 429 / 502 / 503 / 504 responses automatically. See Advanced · Retries. Errors that surface to your code have already been retried up to maxNetworkRetries times (default 3).

429 honors the server’s Retry-After header during the SDK’s internal retries. If retries are exhausted, the final MedblocksRateLimitError carries retryAfter for your own backoff logic.