API Errors

Standard error envelope, HTTP statuses, and the stable type and code the API and SDK share.

Every non-2xx Platform API response returns the same envelope shape.

{
  "error": {
    "type": "authentication_error",
    "code": "invalid_api_key",
    "message": "API key invalid",
    "param": null,
    "doc_url": "https://medblocks.com/docs/reference/errors",
    "request_id": "9c9b6f7a-8e4f-4a3b-9c1e-6f3a2d8b7c4d"
  }
}

Log error.code, error.message, the HTTP status, and error.request_id. Show patients a friendly retry message instead of raw portal or OAuth text.

Error Fields

Every error carries the same fields. The wire envelope names them in snake_case under error, and the SDK exposes them as camelCase properties on the thrown MedblocksError. The values are identical, so branch on the same type and code either way.

Wire (JSON)SDKTypeDescription
error.typetypestringBroad category such as authentication_error, invalid_request_error, or ehr_error.
error.codecodestringStable machine-readable code. Branch on this.
error.messagemessagestringHuman-readable explanation for developers and logs.
error.paramparamstring or nullRequest field or parameter related to the error, when available.
error.request_idrequestIdstringCorrelation ID. Include it in support tickets.
error.doc_urldocUrlstringLink to this API errors reference. Same URL for every code.
HTTP statusstatusCodenumberThe HTTP status. Read from the status line, not the JSON body.
Retry-After headerretryAfternumber or nullSeconds to wait, set on rate_limit_error, null otherwise.

HTTP Statuses

StatusMeaning
400Validation error, malformed request, or unsupported API version.
401Missing, invalid, or expired API key.
402Your plan doesn’t cover this. Either the feature isn’t part of your plan (feature_not_enabled), or you’ve hit your patient limit (quota_exceeded).
403API key is valid but does not have permission for the resource.
404Resource was not found in your organization.
409Resource conflict, such as a duplicate identifier.
413Request payload is too large.
429Rate limit or quota exceeded.
500Unexpected Medblocks server error. Retry with backoff.
502Upstream EHR, OAuth, email, token, or storage service failed.

Error Codes

type is the broad category, code is the exact reason, and each row lists the HTTP status the response carries. Branch on code, the same string in the wire envelope and the SDK.

TypeCodeHTTPTypical Cause
authentication_errornot_authenticated401The request is not authenticated.
authentication_errorinvalid_credentials401Login or credential verification failed.
authentication_errorsession_expired401A user session expired.
authentication_errormissing_api_key401Authorization header is missing.
authentication_errorinvalid_api_key401API key is invalid.
authentication_errorexpired_api_key401API key is expired.
permission_errorforbidden403Authenticated caller cannot access the operation.
permission_errororg_access_denied403Caller is not a member of the active organization.
permission_errorrole_insufficient403User role is not allowed to perform the operation.
permission_errorpatient_access_denied403Patient session is required or invalid.
permission_errorscope_violation403Resource is outside the active organization.
permission_errorinsufficient_scope403API key lacks the required scope.
invalid_request_errorbad_request400Request is malformed or missing required fields.
invalid_request_errorinvalid_data400Request data failed validation.
invalid_request_errorpayload_too_large413Request body is too large.
invalid_request_errorinvalid_image400Image data or format is invalid.
invalid_request_errorunsupported_api_version400Version header is not supported.
invalid_request_erroroauth_state_expired400OAuth state expired before callback completion.
not_found_errorresource_not_found404Patient, patient session, or source was not found.
conflict_errorresource_conflict409Request conflicts with existing data.
conflict_erroralready_linked409Resource is already linked to another entity.
conflict_errorexternal_id_already_exists409External identifier already exists in the organization.
rate_limit_errorthrottled429Too many requests.
rate_limit_errorquota_exceeded429API key quota is exceeded.
rate_limit_errorapi_key_limit_exceeded429Organization has reached its active API key cap. Revoke an existing key first.
billing_errorfeature_not_enabled402Workspace plan does not include the requested feature.
billing_errorquota_exceeded402Free-tier active-patient limit has been reached.
ehr_errorfhir_error502Upstream FHIR service failed.
ehr_erroroauth_error502Upstream OAuth provider failed.
ehr_erroremail_error502Email delivery failed.
ehr_errortoken_exchange_failed502Medblocks could not exchange an OAuth code for tokens.
ehr_errortoken_unavailable502No valid token is available for the upstream service.
ehr_errorstorage_error502Cloud storage operation failed.
api_errorinternal_error500Unexpected Medblocks server error.
api_errordb_error500Database operation failed.
api_errorconfig_missing500Required server configuration is missing.

Handling Errors

The SDK throws a single MedblocksError for every failed request. Discriminate on err.type and err.code, the same strings the API returns. Use the isMedblocksError guard to tell an API error apart from a native or network failure, then re-throw what the guard rejects.

handle-errors.ts
import { isMedblocksError } from "medblocks";

try {
  await mb.patientSession.init(input);
} catch (err) {
  if (!isMedblocksError(err)) throw err; // a native error, such as a network failure
  if (err.type === "authentication_error") {
    // 401, alert on a bad or expired key
  } else if (err.type === "invalid_request_error") {
    console.error("invalid", { param: err.param, code: err.code });
  } else if (err.type === "rate_limit_error") {
    // 429, wait err.retryAfter seconds
  } else {
    console.error("medblocks error", { code: err.code, requestId: err.requestId });
  }
}

A type this SDK version predates still arrives as a MedblocksError, so the final else catches it and a future category never slips past isMedblocksError. A transport failure that outlives the SDK’s retries is the 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, which is why the example re-throws it.

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

log-errors.ts
import { isMedblocksError } from "medblocks";

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

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

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 MedblocksError still carries retryAfter for your own backoff.

Webhook Signature Errors

MedblocksSignatureError is not an API response error. Medblocks.webhooks.constructEvent throws it while verifying a webhook delivery, entirely client-side, so it has no HTTP status, no envelope, and no doc_url. Read err.reason to tell the failure modes apart, and return 400 on any of them.

The full reason table and a constructEvent handler live on the webhook signatures page.

Need help?

Running into an issue? Get support from our team and we will get back to you.

How is this guide?