Skip to content

Onboard from your app

Your app already has the data: a phone app reads a watch’s heart rate, a web app holds a person’s notes. Write it into the person’s vault, and any agent or workflow they connect to that scope can work on it. Onboarding needs no agent and no agreement: the person writes their own data into their own vault, under their own key.

The running example: an iOS app writes heart-rate readings into a health scope.

Before you start

  • @cef-ai/vault-sdk 5.5.0.
  • A Signer for the person: CereWallet.fromMnemonic(...) or CereWallet.fromKeystore(...) for the wallet on the device, or KeypairWallet.fromSeed(...) for a server acting as the person.
  • Your vault API URL and chain URL.

1. Construct the client with the person’s signer

import { VaultSDK, CereWallet } from "@cef-ai/vault-sdk";
const sdk = new VaultSDK({
endpoint: config.vaultUrl,
chainUrl: config.chainUrl,
signer: await CereWallet.fromMnemonic(mnemonic),
});

Every request is now signed with the person’s key, so the vault authorizes it because it descends from their wallet, not from a secret you hold.

2. Open their vault

const vault = await sdk.vault.ensure({ name: "My Vault" });

On first use this claims and provisions the vault; afterwards it returns the existing one. If it throws OnboardingRequiredError, run @cef-ai/account’s provisioning.ensure and retry. See Work with the Vault SDK.

3. Give the data its own scope

A scope of its own lets the person connect a fitness agent to exactly this data and nothing else.

async function ensureScope(name: string, displayName: string) {
const existing = await vault.scopes.list();
if (!existing.some((s) => s.name === name)) {
await vault.scopes.create({ name, displayName });
}
}
await ensureScope("health", "Health");

4. Publish the readings

Each reading is one event. Use one context per metric so each history is its own stream, and stable, namespaced types: agents subscribe on them, and the payload shape is the contract their handlers read.

const health = vault.scope("health");
for (const s of await readWatchSamples()) {
await health.publish({
type: "health.heart_rate",
context: "heart-rate",
payload: { bpm: s.bpm, measuredAt: s.timestamp },
});
}

publish sends one event and throws if the vault rejects it, so a resolved call means the reading is stored. Track what you already sent on your side: the same reading published twice is two events.

5. Store files as objects

For files (a workout export, an audio note), upload an object and announce it in the same call:

await health.objects.upload(`exports/${day}.json`, bytes, {
contentType: "application/json",
publishEvent: { type: "health.export", context: "exports", payload: { day } },
});

The event’s payload gets the object’s vaultPath, so an agent can fetch it.

6. Read back what you wrote

const { items: streams } = await health.streams.list();
const page = await health.stream("heart-rate").events.list({ limit: 50 });
for (const e of page.items) console.log(e.type, e.payload);

Pass page.nextCursor as cursor while page.hasMore is true.

7. Let an agent work on it

The data is in the vault. To have an agent or workflow act on it, the person connects it to the same scope. That signs an agreement, so the SDK also needs garEndpoint:

await vault.agents.connect({
agentId: "<agentServicePubkey>:fitness-coach",
scope: "health",
});

From here the agent reacts to every reading you publish. See Connect to a vault.