Repository navigation
Error Handling
Ivan William edited this page Aug 29, 2026
·
1 revision
Error classification and handling strategies for SATUSEHAT FHIR API integration.
const resp = await ss.post('Patient', patientPayload);
if (!resp.isSuccess()) {
for (const msg of resp.getErrorMessages()) {
console.log(`API Error: ${msg}`);
}
}import { ErrorCategory } from '@satusehat/integration-node';
const errCategory = ErrorClassifier.classify(resp);
switch (errCategory) {
case ErrorCategory.DUPLICATE:
// Resource already exists
break;
case ErrorCategory.VALIDATION:
// Field validation failed
break;
case ErrorCategory.TERMINOLOGY:
// Code not in valueset
break;
case ErrorCategory.REFERENCE:
// Reference resolution failed
break;
case ErrorCategory.AUTHENTICATION:
await oauth2.refreshToken();
break;
case ErrorCategory.RATE_LIMIT:
// Wait and retry
break;
case ErrorCategory.SERVER:
// Retry with backoff
break;
}| Category | HTTP | Description | Action |
|---|---|---|---|
Duplicate |
400 | Resource already sent | Check existence first |
Validation |
400 | Missing/invalid field | Fix payload |
Terminology |
400 | Invalid code | SATUSEHAT valueset |
Reference |
400 | Missing referenced resource | Create prerequisite first |
Authentication |
401 | Token expired | Refresh token |
Authorization |
403 | Insufficient permissions | Check scopes |
NotFound |
404 | Resource not found | Verify IHS |
RateLimit |
429 | Too many requests | Wait Retry-After
|
Server |
5xx | Server error | Retry with backoff |
const body = resp.getBody();
const issues = body['issue'] as Array<Record<string, unknown>>;
for (const issue of issues) {
const severity = issue['severity'] as string;
const details = (issue['details'] as Record<string, unknown>)['text'] as string;
const expressions = issue['expression'] as string[] | undefined;
console.log(`[${severity}] ${details}`);
if (expressions) {
for (const expr of expressions) {
console.log(` Field: ${expr}`);
}
}
}async function sendWithRetry(
ss: SSRequest,
resource: string,
payload: Record<string, unknown>,
maxRetries = 5
): Promise<SSResponse> {
for (let attempt = 0; attempt < maxRetries; attempt++) {
const resp = await ss.post(resource, payload);
if (resp.isSuccess()) return resp;
const category = ErrorClassifier.classify(resp);
// Non-retryable — return immediately
if (['DUPLICATE', 'VALIDATION', 'TERMINOLOGY', 'REFERENCE', 'AUTHORIZATION', 'NOT_FOUND'].includes(category)) {
return resp;
}
// Retry with backoff
const backoff = Math.min(2 ** attempt, 60) * 1000;
await new Promise(resolve => setTimeout(resolve, backoff));
}
throw new Error('Max retries exceeded');
}import { RateLimiter } from '@satusehat/integration-node';
const limiter = new RateLimiter({ rpm: 60, burst: 10 });
for (const patient of patients) {
await limiter.wait();
const resp = await ss.post('Patient', patient);
}-
Always check
isSuccess()— HTTP 200 doesn't guarantee API success - Extract RuleNumber from error messages
- Prerequisite chain: Organization → Location → Practitioner → Patient → Encounter → Clinical
- Retry on 429/5xx — never retry on 4xx
- Use RateLimiter — prevent 429 proactively
- Bundle transactions — batch related resources