Errors

Branch on typed errors. Read the request id you put in support tickets, and let the SDK retry the transient ones.

Every failed request throws a typed error. Each one extends MedblocksError, carries the same set of fields, and maps to one HTTP status, so you branch on the specific case with instanceof and let the base class catch the rest.

catch-shape.ts
import { MedblocksError } from "@medblocks/connect";

try {
  const session = await mb.patientSession.init(input);
} catch (err) {
  if (err instanceof MedblocksError) {
    // every API error lands here, typed
    console.error(err.code, err.requestId);
  }
  throw err;
}

Error types

The hierarchy has one base class and eight subclasses, each pinned to a type and an HTTP status. Import the ones you handle and check them with instanceof.

ClassStatusWhen it fires
MedblocksErroranyThe base class. Catch it as a fallback.
MedblocksAuthenticationError401Missing, invalid, or expired API key.
MedblocksPermissionError403The key is valid but not allowed here, for example a cross-org read or a missing scope.
MedblocksInvalidRequestError400A field failed validation. param names it.
MedblocksNotFoundError404The resource doesn’t exist for your organization.
MedblocksConflictError409A duplicate external_id or a state conflict.
MedblocksRateLimitError429Over quota. retryAfter holds the wait.
MedblocksEhrError502An upstream service failed, such as the EHR, OAuth, or storage.
MedblocksApiError500An unexpected error on the Medblocks side.

A transport failure that outlives the SDK’s retries is the one exception. A dropped connection, a DNS failure, or a timeout surfaces as the runtime’s own error, such as a TypeError or an abort, not a MedblocksError. That’s why the example above re-throws what it doesn’t recognize.

MedblocksSignatureError is separate. It comes from webhook verification, never from an API call, and has no HTTP status. See Webhook signature errors.

Error fields

Every MedblocksError instance carries the same fields, parsed from the API’s error envelope.

FieldTypeWhat it is
messagestringA human-readable explanation.
codestringA stable, machine-readable code. Branch on this.
typestringThe error category, one of the type values above.
paramstring | nullThe offending field, when known.
requestIdstringCorrelation id from the X-Request-Id header. Put it in support tickets.
statusCodenumberThe HTTP status.
docUrlstringA link to the docs for this code.

MedblocksRateLimitError adds one field, retryAfter. It’s the wait in seconds parsed from the Retry-After header, or null if the server didn’t send one.

Handling errors

Catch the cases you treat differently, and let the base class catch everything else. Order matters, so check the subclasses before MedblocksError.

handle-errors.ts
import {
  MedblocksAuthenticationError,
  MedblocksInvalidRequestError,
  MedblocksRateLimitError,
  MedblocksError,
} from "@medblocks/connect";

try {
  await mb.patientSession.init(input);
} catch (err) {
  if (err instanceof MedblocksAuthenticationError) {
    // 401, alert on a bad or expired key
  } else if (err instanceof MedblocksInvalidRequestError) {
    console.error("invalid", { param: err.param, code: err.code });
  } else if (err instanceof MedblocksRateLimitError) {
    // 429, wait err.retryAfter seconds
  } else if (err instanceof MedblocksError) {
    console.error("medblocks error", { code: err.code, requestId: err.requestId });
  } else {
    throw err; // a native error, such as a network failure
  }
}

The base MedblocksError also covers any error type a future API version adds. A new type arrives as a base instance instead of slipping past your instanceof checks.

Common codes

Each class carries a stable code you branch on for the exact reason. These are the ones you’ll see most often.

CodeClassMeaning
missing_api_keyMedblocksAuthenticationErrorNo API key on the request.
invalid_api_keyMedblocksAuthenticationErrorThe key is wrong or revoked.
expired_api_keyMedblocksAuthenticationErrorThe key aged out.
insufficient_scopeMedblocksPermissionErrorThe key lacks the scope this call needs.
bad_requestMedblocksInvalidRequestErrorA field failed validation.
unsupported_api_versionMedblocksInvalidRequestErrorThe pinned Version isn’t supported on this key.
resource_not_foundMedblocksNotFoundErrorNo such resource in your organization.
external_id_already_existsMedblocksConflictErrorAn external_id is already taken in your org.
throttledMedblocksRateLimitErrorToo many requests.
quota_exceededMedblocksRateLimitErrorThe key’s quota is spent.
oauth_errorMedblocksEhrErrorThe hospital’s OAuth provider failed.
fhir_errorMedblocksEhrErrorThe upstream FHIR service failed.
internal_errorMedblocksApiErrorAn unexpected error on the Medblocks side.

The full list lives in the API errors reference.

Webhook signature errors

Medblocks.webhooks.constructEvent verifies an incoming webhook delivery before it hands you the event. When verification fails it throws MedblocksSignatureError. This is purely client-side, so the error has no statusCode and no network call was made. Read reason to tell the failure modes apart.

reasonWhat it means
missing_headerNo Medblocks-Signature header on the request.
malformed_headerThe header didn’t match t=<sec>,v1=<hex>.
timestamp_expiredThe signature timestamp is outside the tolerance window, 5 minutes by default.
signature_mismatchThe HMAC didn’t match, so the wrong secret was used or the body was tampered with.
malformed_bodyThe body wasn’t valid JSON in the event envelope shape.

Catch it and return 400, since a delivery that fails verification is one you don’t trust. constructEvent is async, so await it.

verify-webhook.ts
import { Medblocks, MedblocksSignatureError } from "@medblocks/connect";

try {
  const event = await Medblocks.webhooks.constructEvent(
    rawBody, // the unparsed request body, as a string
    req.headers["medblocks-signature"],
    process.env.MEDBLOCKS_WEBHOOK_SECRET,
  );
  // event.type is narrowed, event.data.object is typed by it
} catch (err) {
  if (err instanceof MedblocksSignatureError) {
    console.error("bad webhook signature", { reason: err.reason });
    return new Response("invalid signature", { status: 400 });
  }
  throw err;
}

Pass the raw, unparsed body. Verification runs an HMAC over the exact bytes, so a re-serialized JSON object won’t match. The webhook signatures page covers the full setup.

Logging

The request id is the most useful field in a support ticket, because it lets us find your exact call. Log it on every error path.

log-errors.ts
import { MedblocksError } from "@medblocks/connect";

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 failures for you before it throws. That covers rate limits (429), upstream errors (502, 503, 504), and network-level failures. It doesn’t retry other 4xx errors or 500, since retrying those won’t help. By the time an error reaches your catch, it has already been retried up to maxNetworkRetries times, three by default.

On a 429, the SDK honors the server’s Retry-After during its own retries. If those run out, the final MedblocksRateLimitError still carries retryAfter for your own backoff.

See also