Using the vault
Store and read PHI and files in the account's vault with client.vault
Records read and written through client.api tokenize their PHI on their own; see
PHI and the vault. client.vault is for everything else: PHI
your app holds outside any record, and files.
It has two parts:
client.vault.patient— a patient's PHI, stored as one row and addressed by avtk_token.client.vault.artifact— files, addressed by avta_token.
Patient PHI
// Store
const { token } = await client.vault.patient.tokenize({
systemId: 'my-app',
systemPatientId: 'P-42',
firstName: 'Jane',
lastName: 'Doe',
dob: '1980-01-02',
});
// Find a patient's token by their id in your system
const found = await client.vault.patient.findTokenByPatientId({
systemId: 'my-app',
systemPatientId: 'P-42',
});
// Read
if (found) {
const phi = await client.vault.patient.detokenize(found.token);
// { systemId, systemPatientId, firstName, lastName, dob, token, createdAt, updatedAt, lastAccessedAt }
}| Call | Returns | Needs |
|---|---|---|
tokenize(payload) | { token, createdAt, updatedAt } | Vault:Write |
detokenize(token) | the stored PHI and the token's timestamps | Vault:Read |
findTokenByPatientId({ systemId, systemPatientId }) | { token, createdAt, lastAccessedAt }, or null when there is no row | Vault:Read |
Which row a token addresses
A token is derived from systemId + systemPatientId and nothing else:
systemIdnames the system the patient id comes from — your app, or the EHR it came from.systemPatientIdis the patient's id in that system.
The same pair always gives the same token, so tokenizing a patient twice does not create a second
row, and findTokenByPatientId finds a row without writing anything. Name and date of birth play
no part in it; to find a patient by demographics, use client.api.patient.lookup.
Changing stored PHI
tokenize replaces the whole row. Tokenizing a patient who already has a row overwrites every
value in it, and a field you leave out is erased. To change one field, read the row, merge, and
write the result:
const { token: _, createdAt, updatedAt, lastAccessedAt, ...current } =
await client.vault.patient.detokenize(token);
await client.vault.patient.tokenize({ ...current, lastName: 'Smith' });What travels
PHI is encrypted in your process before it is sent, and a reveal is encrypted to a key created for
that one call, so the Vim API only ever holds ciphertext. Every detokenize and
findTokenByPatientId is audited against the account.
Files
import { readFile } from 'node:fs/promises';
const { token } = await client.vault.artifact.upload(await readFile('chart.pdf'), {
contentType: 'application/pdf',
filename: 'chart.pdf',
retentionDays: 30,
});
const meta = await client.vault.artifact.get(token); // contentType, filename, sizeBytes, status
const { url } = await client.vault.artifact.getDownloadUrl(token); // short-lived GET URL
await client.vault.artifact.delete(token);| Call | Returns | Needs |
|---|---|---|
upload(bytes, params?) | { token } once storage has confirmed the bytes | Vault:WriteArtifact |
create(params?) | { token, uploadUrl, uploadHeaders, expiresAt }, for uploading from elsewhere | Vault:WriteArtifact |
get(token) | the file's metadata | Vault:ReadArtifact |
getDownloadUrl(token) | { url, expiresAt } | Vault:ReadArtifact |
delete(token) | — | Vault:DeleteArtifact |
- Only your own files. Your app reaches the files it uploaded and no others.
- The bytes skip the Vim API. They go to and from storage over presigned URLs.
uploadcomputes the file's SHA-256 and storage checks it, so a corrupted upload is refused. createis the low-level half ofupload. Use it when the bytes are somewhere else: PUT them touploadUrlwithuploadHeadersexactly as returned, beforeexpiresAt.- Uploads have their own time limit,
uploadTimeoutMs(5 minutes by default), nottimeoutMs. See Configuration.
Permissions and errors
What your app may call is what the account granted it; anything else is refused with a 403.
A service app gets Vault:Read and Vault:Write on patient PHI from the operations an account
approves when it installs the app. The artifact permissions are not issued through an install
today.
Every failure is a VimConnectError. A failed upload carries the code artifact_upload_failed and
its token in details.token; that token holds no file, so call upload again for a new one.