> ## Documentation Index
> Fetch the complete documentation index at: https://developer.eka.care/llms.txt
> Use this file to discover all available pages before exploring further.

# Records

> Upload, list, group, download, edit and delete medical documents with the Medical Records Web SDK

Every records method needs two IDs:

* `bid` — your business ID, provided by Eka Care when your account is set up.
* `patientId` — the OID of the logged-in user whose records you are working with.

```typescript theme={null}
const BID = 'your-business-id';
const OID = 'logged-in-user-oid';
```

## Linked profiles

`patientId` is the **primary OID** — the profile that owns writes. Add, edit and delete always use it (sent as the `X-Pt-Id` header). To also surface records and cases belonging to other profiles (e.g. dependents or family members), pass `linkedPatientIds`. Read, list and group methods then return the **union** across `[patientId, ...linkedPatientIds]`, and the background sync fans out one fetch per OID. Omit `linkedPatientIds` for single-profile behavior — nothing changes.

```typescript theme={null}
const docs = await sdk.listDocuments({
  bid: BID,
  patientId: OID,                                           // primary — owns writes
  linkedPatientIds: ['dependent-oid-1', 'dependent-oid-2'], // also read these
});
```

Supported on `listDocuments`, `listLocalDocuments`, `groupDocuments`, `getAllTags`, `listCases` and `listLocalCases`. Writes (`addDocument`, `editDocument`, `deleteDocument`, `createCase`, …) always use the primary `patientId`.

## List Documents

`listDocuments` is **cache-first**: it gives you local data instantly, then updates it from the server.

What happens when you call it:

1. **Local read** — the SDK reads the records already stored in IndexedDB.
2. **Instant callback** — if you passed `onStale`, those local records are handed to you right away, so your UI can render without waiting for the network.
3. **Server fetch** — the SDK then asks the server only for records that changed since the last sync (not the whole list again) and saves them into IndexedDB. Anyone subscribed to `documents:changed` gets notified.
4. **Final result** — the `await` completes with the full, up-to-date list. If there is no internet, you still get the local list.

<Note>
  If you don't pass `onStale`, there is no early delivery — the call waits for
  the server fetch to complete and then returns the complete list in one go.
</Note>

```typescript theme={null}
import { SortBy } from '@eka-care/medical-records-ts-sdk';

// All documents
const docs = await sdk.listDocuments({ bid: BID, patientId: OID });

// With limit and sort
const docs = await sdk.listDocuments({
  bid: BID, patientId: OID,
  limit: 20,                        // default: no limit
  sortBy: SortBy.CreatedAt,         // SortBy.UpdatedAt (default) | SortBy.CreatedAt
});

// Filter by document type
const labReports = await sdk.listDocuments({
  bid: BID, patientId: OID,
  filter: { documentType: 'lr' },   // lr | ps | dc | vc | in | iv | sc | ot — from the hub API
});

// Filter by case
const caseRecords = await sdk.listDocuments({
  bid: BID, patientId: OID,
  filter: { caseId: 'case-uuid' },
});

// Stale-while-revalidate — show cached instantly, update when fresh arrives
const docs = await sdk.listDocuments({
  bid: BID, patientId: OID,
  onStale: (cached) => setDocs(cached),
});
setDocs(docs);

// Force a full refresh — ignore the local watermark and re-fetch every record
const docs = await sdk.listDocuments({ bid: BID, patientId: OID, forceRefresh: true });
```

**`ListDocumentsFilter` fields:**

| Field | Type | Description |
| - | - | - |
| `documentType` | `string` | Filter by document type code (`'lr'`, `'ps'` etc.) — client-side |
| `caseId` | `string` | Filter to records belonging to a case — client-side |
| `fileType` | `string` | Filter by file type (`'IMG'`, `'PDF'`, `'HTML'`) — client-side |
| `tags` | `string[]` | Only records that have **all** of these tags — client-side |
| `maxFileTypeFetch` | `number` | Max documents to collect when `fileType` is set. Default: `3` |
| `updatedAfter` | `number` | Only records updated after this epoch (seconds) — sent to the server |

### The document object

Each item returned by `listDocuments` / `listLocalDocuments` is a `DocumentItem`. The most commonly used fields:

| Field | Type | Description |
| - | - | - |
| `document_id` | `string` | Server-assigned document UUID |
| `document_type` | `string` | Document type code (e.g. `'lr'`, `'ps'`) |
| `patient_id` | `string` | Profile OID that owns the document (primary or a linked profile) |
| `cases` | `string[]` | Case IDs this document is linked to |
| `upload_date` | `number` | Upload timestamp (epoch seconds) |
| `metadata` | `object` | `thumbnail`, `document_date`, `title`, `tags`, `auto_tags`, `abha` |
| `file_type` | `string` | Preferred file format for this call (`'PDF'`, `'IMG'`, `'HTML'`) |
| `file_types` | `string[]` | All known file formats (served from local DB) |
| `is_smart` | `boolean` | A smart (AI-extracted) report is available |
| `is_analyzing` | `boolean` | AI analysis is currently in progress |
| `total_vitals` | `number` | Count of vitals/parameters extracted from the document |
| `out_of_range_vitals` | `number` | How many of those vitals fall outside their normal reference range |
| `sync_state` | `SyncState` | Local sync state — present when served from IndexedDB cache |

## Group Documents

`groupDocuments` organises the document list into sections, ready to render as a sectioned UI (like a list with headers). Instead of one flat array, you get an array of groups — each with a `key`, a display `label`, and the `records` that fall under it.

You can group two ways:

* **By document type** — one group per type code: all lab reports together, all prescriptions together, and so on.
* **By date** — one group per day, month, or year (e.g. "May 2025"), based on either the document date or the upload date.

Reads from the local DB — no network call.

```typescript theme={null}
import { GroupBy, DateGroupFormat, DateField } from '@eka-care/medical-records-ts-sdk';

// Group by document type
const groups = await sdk.groupDocuments({
  bid: BID, patientId: OID,
  groupBy: GroupBy.DocumentType,
});
// → [{ key: 'lr', label: 'LR', records: [...] }, ...]   — label = upper-cased type code

// Group by month (default)
const groups = await sdk.groupDocuments({
  bid: BID, patientId: OID,
  groupBy: GroupBy.Date,
  dateFormat: DateGroupFormat.Month,   // Day | Month (default) | Year
  dateField: DateField.DocumentDate,   // DocumentDate (default) | CreatedAt
});
// → [{ key: '2025-05', label: 'May 2025', records: [...] }, ...]
```

**`GroupDocumentsOptions`:**

| Field | Type | Default | Description |
| - | - | - | - |
| `groupBy` | `GroupBy` | required | `GroupBy.DocumentType` or `GroupBy.Date` |
| `dateFormat` | `DateGroupFormat` | `Month` | `Day` \| `Month` \| `Year` — granularity when grouping by date |
| `dateField` | `DateField` | `DocumentDate` | `DocumentDate` \| `CreatedAt` — which date to group by |
| `sortBy` | `SortBy` | `UpdatedAt` | `UpdatedAt` \| `CreatedAt` — sort within each group |
| `filter` | `ListDocumentsFilter` | — | Same filters as `listDocuments` |

## Upload a Document

```typescript theme={null}
const file = input.files[0];

await sdk.addDocument({
  bid: BID, patientId: OID,
  batchRequests: [{
    documentType: 'lr',
    documentDate: Math.floor(Date.now() / 1000),
    cases: ['case-uuid'],                        // optional — assign to case
    files: [{ contentType: 'application/pdf', file_size: file.size }],
  }],
  files: [[file]],
  filenames: [[file.name]],
});
```

<Note>
  **Offline:** record + blob are saved to IndexedDB as `upload_failure` and
  auto-retried on the next `sdk.sync()`.
</Note>

**File constraints:**

| | Limit |
| - | - |
| Max batch items | 5 |
| Max files per batch | 10 |
| Image max | 10 MB |
| PDF max | 25 MB |
| Supported types | `image/jpeg` `image/jpg` `image/png` `application/pdf` |

## Download a Document

Cache-first — returns blobs from IndexedDB if previously fetched, otherwise downloads from S3 and caches.

```typescript theme={null}
const files = await sdk.downloadFile({ bid: BID, patientId: OID, documentId: 'abc-uuid' });

for (const file of files) {
  console.log(file.blob);       // Blob — PDF / image / HTML
  console.log(file.fileType);   // DocumentFileType — 'IMG' | 'PDF' | 'HTML'
  console.log(file.filename);   // e.g. 'abc12345_0.pdf'
  console.log(file.fromCache);  // true = served from IndexedDB, zero network
}

// Trigger browser download
const a = document.createElement('a');
a.href = URL.createObjectURL(files[0].blob);
a.download = files[0].filename;
a.click();
```

## Get Smart Report

Cache-first — returns the AI-extracted smart report from IndexedDB if available, otherwise fetches from the API.

```typescript theme={null}
const report = await sdk.getSmartReport({
  bid: BID, patientId: OID,
  documentId: 'abc-uuid',
});

if (report) {
  console.log(report.verified);    // AI-verified fields
  console.log(report.unverified);  // AI-unverified fields
}
// null = document has no smart report
```

## Vital & Lab Parameter Trends

`getParameterTrend` returns every logged reading for a single vital or lab parameter (e.g. Hemoglobin) across all of a patient's reports, so you can plot a trend graph. The `parameterId` comes from a smart report item's `eka_id` (or `vital_id`).

<Note>
  This is **network-only** — it hits the vault host and is not cached. Scope it to the profile
  that owns the vital (the document's `patient_id`).
</Note>

```typescript theme={null}
import { VitalResultFilter } from '@eka-care/medical-records-ts-sdk';

const trend = await sdk.getParameterTrend({
  bid: BID,
  patientId: OID,                    // profile that owns the vital
  parameterId: 'lb-2139115007',      // eka_id / vital_id from a smart report item
  filter: VitalResultFilter.All,     // All (default) | High | Low | Normal — filters vitals_info
});

console.log(trend.name);   // "Hemoglobin (Hb)"
console.log(trend.unit);   // "g/dL"

for (const log of trend.vitals_info) {
  console.log(log.val, log.display_result, log.display_time); // 13.1, "Normal", "03 Jan'26"
  console.log(log.range?.display_text);                       // "12.0 - 15.0"
  // log.document_id — pass to describeDocument to open the source report
}
```

**`ParameterTrendResponse` fields:**

| Field | Type | Description |
| - | - | - |
| `name` | `string` | Display name of the parameter (e.g. `'Hemoglobin (Hb)'`) |
| `eka_id` | `string` | Eka id of the parameter |
| `unit` | `string` | Unit label (e.g. `'g/dL'`) |
| `unit_eka_id` | `string` | Eka unit id |
| `vitals_info` | `VitalLog[]` | All logged readings, newest first — each with `val`, `date`, `display_result`, `range` and the source `document_id` |

## Describe a Document

Returns the full details of a document. Cache-first — served from IndexedDB if the document's files were previously fetched, otherwise fetched from the API.

```typescript theme={null}
const detail = await sdk.describeDocument({
  bid: BID, patientId: OID,
  documentId: 'abc-uuid',
  preferenceType: 'PDF',     // optional — preferred file format ('PDF' | 'IMG' | 'HTML')
});
```

**`DescribeDocumentResponse` fields (all optional):**

| Field | Type | Description |
| - | - | - |
| `files` | `DocumentFile[]` | File attachments — each has `asset_url` (signed S3 URL) and `file_type` (`DocumentFileType`) |
| `smart_report` | `SmartReport` | AI-extracted fields (`verified` / `unverified`) |
| `tags` | `string[]` | User-supplied tags |
| `document_type` | `string` | Document type code (e.g. `'lr'`, `'ps'`) |
| `document_date_epoch` | `number` | Document date as Unix epoch (seconds) |
| `source` | `string` | Origin of the document |

## Edit a Document

Local-first — patches the DB immediately, syncs to the server in the background.

```typescript theme={null}
await sdk.editDocument({
  bid: BID, patientId: OID,
  documentId: 'abc-uuid',
  data: {
    documentType: 'ps',                  // new document type
    documentDate: 1748000000,            // new document date (epoch seconds)
    cases:        ['case-id-1'],         // link to cases (replaces existing)
    tags:         ['blood', 'routine'],  // tags (replaces existing)
  },
});
```

## Delete a Document

Soft-deletes locally (hidden immediately), hard-deletes after the server confirms. Also removes the document from any linked cases in the local DB.

```typescript theme={null}
await sdk.deleteDocument({ bid: BID, patientId: OID, documentId: 'abc-uuid' });
```

## Tags

Tags are set on a document via `editDocument` (`data.tags` — see [Edit a Document](#edit-a-document)). Two read helpers query them from the local DB — no network call:

```typescript theme={null}
// Every unique tag across all documents — sorted alphabetically (e.g. for a filter dropdown)
const allTags = await sdk.getAllTags({ bid: BID, patientId: OID });

// Tags on a single document
const tags = await sdk.getDocumentTags({ bid: BID, patientId: OID, documentId: 'abc-uuid' });
```

`getAllTags` also accepts `linkedPatientIds` to span [linked profiles](#linked-profiles).


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.