Production access is free and self-serve with an account. · Synthetic demo · no account needed

FinchNode

Patient-authorized EHR integration

TEFCA sandbox API

Build an app that discovers and retrieves synthetic TEFCA-style records with your FinchNode sandbox key.

Use an application sandbox key to run the synthetic discovery, retrieval, export, and revocation flow from your own backend. The data comes from FinchNode's fictional demo patient. No identity provider or exchange network is contacted.

The provider test exchange remains in the signed-in TEFCA console. Its base64 document format differs from this simulation's FHIR collection.

Start with a sandbox key

Create an application in Applications, allow medications and labs, and create a sandbox key. Keep it in your server's FINCHNODE_API_KEY environment variable. A ck_live_ key, delegated agent credential, or conformance-run key cannot use these endpoints.

Send requests to https://api.finchnode.com/api/v1. These routes use the usual API authentication and errors, including request IDs and rate-limit headers.

Run a complete backend example

With Node 24 and FINCHNODE_API_KEY set, save this as tefca-demo.mjs and run node tefca-demo.mjs. The synthetic consent acceptance below applies only to this fictional demonstration.

const base = 'https://api.finchnode.com/api/v1/tefca';
const key = process.env.FINCHNODE_API_KEY;
if (!key?.startsWith('ck_test_')) throw new Error('Set a sandbox FINCHNODE_API_KEY.');
async function request(path, body) {
  const response = await fetch(base + path, {
    method: body === undefined ? 'GET' : 'POST',
    redirect: 'error', signal: AbortSignal.timeout(15000),
    headers: { Authorization: `Bearer ${key}`, 'Content-Type': 'application/json' },
    ...(body === undefined ? {} : { body: JSON.stringify(body) }),
  });
  const result = await response.json();
  if (!response.ok) throw new Error(`${response.status}: ${result.error?.message}`);
  return result;
}
const config = await request('/config');
const session = await request('/sessions', {
  mode: 'simulation', patientId: config.patientId,
  consent: true, consentVersion: config.consentVersion,
  scenario: 'matched', categories: ['medications', 'labs'],
});
try {
  const found = await request(`/sessions/${session.id}/discover`, {});
  if (!found.documents.length) {
    console.log('No records:', found.status, found.failures);
  } else {
    const retrieved = await request(`/sessions/${session.id}/retrieve`, {
      documentIds: found.documents.map(document => document.id),
    });
    if (retrieved.results.length) {
      const exported = await request(`/sessions/${session.id}/export`);
      console.log(JSON.stringify(exported.bundle, null, 2));
    }
    console.log('Document failures:', retrieved.failures);
  }
} finally {
  await request(`/sessions/${session.id}/revoke`, {});
}

Try the browser sample app

Download and extract the sample app. Run node server.mjs in that directory with your sandbox key in the environment, then open http://127.0.0.1:4318. The app needs Node 24 and no package installation.

The sample has scenario selection, a synthetic consent checkbox, record-group selection, a FHIR viewer, JSON download, and revocation. It binds to loopback and keeps the key on its server. It is a local hackathon starter; add user authentication and durable browser-session storage before hosting your own app publicly.

Understand the contract

Request Result
GET /tefca/config Current synthetic consent, scenarios, and categories your app can request
POST /tefca/sessions A session owned by your application, valid for 15 minutes
GET /tefca/sessions/{id} The current session state
POST /tefca/sessions/{id}/discover Matching synthetic record groups, or a no-match/source-unavailable outcome
POST /tefca/sessions/{id}/retrieve Selected groups in results, and per-group failures
GET /tefca/sessions/{id}/export One FHIR collection in bundle, plus consent and failures
POST /tefca/sessions/{id}/revoke A revoked session; future discovery, retrieval, and export return 410

Discover and revoke take an empty body. Retrieve takes only documentIds from discovery. Creating a session accepts only the fields in the example, with optional scenario and categories. Omitting categories uses supported categories on the application's allowlist. Never submit personal demographics.

The export contains each retrieved resource once, including one Patient resource. Without the demographics category, that Patient contains only its resource type and synthetic ID. Labs exclude vital signs, even though both use FHIR Observation resources. Results remain separate from /users/{subject}/records; use the TEFCA export directly in your app.

Test failures and recovery

  • matched: records are available.
  • partial: the first selected group fails; other selected groups remain usable. Select at least two groups to see a partial success.
  • no-match: discovery succeeds with no record groups.
  • unavailable: discovery reports a simulated unavailable source.

Requests complete synchronously. A failed or no-match session is a valid scenario outcome, not an HTTP failure. Check status, results, and failures before exporting.

There are at most 20 active sessions per application, shared across its keys. Revoke finished sessions to release capacity. Session creation is not idempotent: keep the returned ID and use the read endpoint to recover. Retrieval of the same selection is repeatable; start a new session to change a completed selection. Expired sessions return 410; old rows are removed when that application starts another session. Revoked rows may then return 404 after removal.

Another application's session returns 404. A narrowed application allowlist returns 403 app_scope_changed; revoke and start again. A concurrent mutation returns 409 session_changed; read the current session before retrying. A 503 tefca_sandbox_unavailable means the deployment is missing this feature's database migration. Revocation cannot remove a copy your app already downloaded.