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) | SDK | Type | Description |
|---|---|---|---|
error.type | type | string | Broad category such as authentication_error, invalid_request_error, or ehr_error. |
error.code | code | string | Stable machine-readable code. Branch on this. |
error.message | message | string | Human-readable explanation for developers and logs. |
error.param | param | string or null | Request field or parameter related to the error, when available. |
error.request_id | requestId | string | Correlation ID. Include it in support tickets. |
error.doc_url | docUrl | string | Link to this API errors reference. Same URL for every code. |
| HTTP status | statusCode | number | The HTTP status. Read from the status line, not the JSON body. |
Retry-After header | retryAfter | number or null | Seconds to wait, set on rate_limit_error, null otherwise. |
HTTP Statuses
| Status | Meaning |
|---|---|
400 | Validation error, malformed request, or unsupported API version. |
401 | Missing, invalid, or expired API key. |
402 | Your 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). |
403 | API key is valid but does not have permission for the resource. |
404 | Resource was not found in your organization. |
409 | Resource conflict, such as a duplicate identifier. |
413 | Request payload is too large. |
429 | Rate limit or quota exceeded. |
500 | Unexpected Medblocks server error. Retry with backoff. |
502 | Upstream 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.
| Type | Code | HTTP | Typical Cause |
|---|---|---|---|
authentication_error | not_authenticated | 401 | The request is not authenticated. |
authentication_error | invalid_credentials | 401 | Login or credential verification failed. |
authentication_error | session_expired | 401 | A user session expired. |
authentication_error | missing_api_key | 401 | Authorization header is missing. |
authentication_error | invalid_api_key | 401 | API key is invalid. |
authentication_error | expired_api_key | 401 | API key is expired. |
permission_error | forbidden | 403 | Authenticated caller cannot access the operation. |
permission_error | org_access_denied | 403 | Caller is not a member of the active organization. |
permission_error | role_insufficient | 403 | User role is not allowed to perform the operation. |
permission_error | patient_access_denied | 403 | Patient session is required or invalid. |
permission_error | scope_violation | 403 | Resource is outside the active organization. |
permission_error | insufficient_scope | 403 | API key lacks the required scope. |
invalid_request_error | bad_request | 400 | Request is malformed or missing required fields. |
invalid_request_error | invalid_data | 400 | Request data failed validation. |
invalid_request_error | payload_too_large | 413 | Request body is too large. |
invalid_request_error | invalid_image | 400 | Image data or format is invalid. |
invalid_request_error | unsupported_api_version | 400 | Version header is not supported. |
invalid_request_error | oauth_state_expired | 400 | OAuth state expired before callback completion. |
not_found_error | resource_not_found | 404 | Patient, patient session, or source was not found. |
conflict_error | resource_conflict | 409 | Request conflicts with existing data. |
conflict_error | already_linked | 409 | Resource is already linked to another entity. |
conflict_error | external_id_already_exists | 409 | External identifier already exists in the organization. |
rate_limit_error | throttled | 429 | Too many requests. |
rate_limit_error | quota_exceeded | 429 | API key quota is exceeded. |
rate_limit_error | api_key_limit_exceeded | 429 | Organization has reached its active API key cap. Revoke an existing key first. |
billing_error | feature_not_enabled | 402 | Workspace plan does not include the requested feature. |
billing_error | quota_exceeded | 402 | Free-tier active-patient limit has been reached. |
ehr_error | fhir_error | 502 | Upstream FHIR service failed. |
ehr_error | oauth_error | 502 | Upstream OAuth provider failed. |
ehr_error | email_error | 502 | Email delivery failed. |
ehr_error | token_exchange_failed | 502 | Medblocks could not exchange an OAuth code for tokens. |
ehr_error | token_unavailable | 502 | No valid token is available for the upstream service. |
ehr_error | storage_error | 502 | Cloud storage operation failed. |
api_error | internal_error | 500 | Unexpected Medblocks server error. |
api_error | db_error | 500 | Database operation failed. |
api_error | config_missing | 500 | Required 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.
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.
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.
Related Articles
- API Overview for authentication and versioning.
- Pagination for cursor paging on list endpoints.
- Medblocks-hosted page for the hosted patient session flow.
- Your own UI for source search and return URL handling.
- Generated API Reference for exact endpoint schemas.
Need help?
Running into an issue? Get support from our team and we will get back to you.
How is this guide?
