Using the vault

Open in Claude

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 a vtk_ token.
  • client.vault.artifact — files, addressed by a vta_ 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 }
}
CallReturnsNeeds
tokenize(payload){ token, createdAt, updatedAt }Vault:Write
detokenize(token)the stored PHI and the token's timestampsVault:Read
findTokenByPatientId({ systemId, systemPatientId }){ token, createdAt, lastAccessedAt }, or null when there is no rowVault:Read

Which row a token addresses

A token is derived from systemId + systemPatientId and nothing else:

  • systemId names the system the patient id comes from — your app, or the EHR it came from.
  • systemPatientId is 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);
CallReturnsNeeds
upload(bytes, params?){ token } once storage has confirmed the bytesVault:WriteArtifact
create(params?){ token, uploadUrl, uploadHeaders, expiresAt }, for uploading from elsewhereVault:WriteArtifact
get(token)the file's metadataVault: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. upload computes the file's SHA-256 and storage checks it, so a corrupted upload is refused.
  • create is the low-level half of upload. Use it when the bytes are somewhere else: PUT them to uploadUrl with uploadHeaders exactly as returned, before expiresAt.
  • Uploads have their own time limit, uploadTimeoutMs (5 minutes by default), not timeoutMs. 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.

On this page