Let your users customize your app with their own data.
Users connect platforms they already use β ChatGPT, Instagram, Gmail, and more β through the Data Connect app, which keeps them in control of what's shared. Your app receives structured, user-consented data through a cryptographically verified grant. No scraping, no OAuth token juggling, no compliance gray areas.
Your App Vana Protocol
ββββββββββββββββββββββββββββββ ββββββββββββββββββββββββββββββ
1. connect({ scopes })
β creates session
β returns deep link βββΆ 2. User opens Data Connect
reviews scopes, exports data,
approves grant
3. Poll resolves with grant βββ Grant signed & registered
4. getData({ grant }) βββΆ 5. Personal Server returns
β structured JSON user data over TLS
The Data Portability Protocol defines how users collect data from platforms, store it under their control (on-device or hosted), and grant third-party apps scoped access. This SDK handles session creation, cryptographic request signing, polling, and data fetching. You write three function calls; the protocol handles the rest.
pnpm add @opendatalabs/connectThis repo is pnpm-only for local development and examples. Use pnpm commands, not npm.
Register an app through Data Connect first. You will need to provide the URL where your app will be deployed, and then be given a private key after registration.
import { connect } from "@opendatalabs/connect/server";
const session = await connect({
privateKey: process.env.VANA_APP_PRIVATE_KEY as `0x${string}`,
scopes: ["chatgpt.conversations"],
webhookUrl: "https://yourapp.com/api/webhook", // optional, data can be pushed to a web hook after a grant is approved
appUserId: "yourapp-user-42", // optional: this is used to corelate your app user with the data they provided
});
// Return to your frontend:
// session.sessionId β used for polling
// session.deepLinkUrl β opens the Data Connect App
// session.expiresAt β ISO 8601 expirationimport { useVanaConnect } from "@opendatalabs/connect/react";
function ConnectData({ sessionId }: { sessionId: string }) {
const { connect, status, grant, deepLinkUrl } = useVanaConnect();
useEffect(() => {
connect({ sessionId });
}, [sessionId]);
if (status === "waiting" && deepLinkUrl) {
return <a href={deepLinkUrl}>Connect your data</a>;
}
if (status === "approved" && grant) {
// grant.grantId, grant.userAddress, grant.scopes are available
return <p>Connected.</p>;
}
return <p>{status}</p>;
}Or use the pre-built button:
import { ConnectButton } from "@opendatalabs/connect/react";
<ConnectButton
sessionId={sessionId}
onComplete={(grant) => saveGrant(grant)}
onError={(err) => console.error(err)}
/>;import { getData } from "@opendatalabs/connect/server";
const data = await getData({
privateKey: process.env.VANA_APP_PRIVATE_KEY as `0x${string}`,
grant, // GrantPayload from step 2
});
// Record<string, unknown> keyed by scope
const conversations = data["chatgpt.conversations"];The Data Connect App verifies your identity by fetching your manifest. Use signVanaManifest() to generate it:
import { signVanaManifest } from "@opendatalabs/connect/server";
// In your manifest route handler (e.g. Next.js /manifest.json/route.ts):
const vanaBlock = await signVanaManifest({
privateKey: process.env.VANA_APP_PRIVATE_KEY as `0x${string}`,
appUrl: "https://yourapp.com",
privacyPolicyUrl: "https://yourapp.com/privacy",
termsUrl: "https://yourapp.com/terms",
supportUrl: "https://yourapp.com/support",
webhookUrl: "https://yourapp.com/api/webhook",
});
const manifest = {
name: "Your App",
short_name: "YourApp",
start_url: "/",
display: "standalone",
vana: vanaBlock, // signed identity block
};Make sure your HTML includes <link rel="manifest" href="/manifest.json">.
| Import | Environment | Exports |
|---|---|---|
@opendatalabs/connect/server |
Node.js | connect(), getData(), signVanaManifest(), low-level clients |
@opendatalabs/connect/react |
Browser | useVanaConnect(), useVanaData(), ConnectButton |
@opendatalabs/connect/core |
Universal | Types, ConnectError, constants |
Creates a session on the Session Relay. Returns sessionId, deepLinkUrl, and expiresAt.
| Param | Type | Required | Description |
|---|---|---|---|
privateKey |
`0x${string}` |
Yes | Builder private key |
scopes |
string[] |
Yes | Data scopes to request |
webhookUrl |
string |
No | Public HTTPS URL for grant event notifications (localhost is rejected) |
appUserId |
string |
No | Your app's user ID for correlation |
Fetches user data from their Personal Server using a signed grant.
| Param | Type | Required | Description |
|---|---|---|---|
privateKey |
`0x${string}` |
Yes | Builder private key |
grant |
GrantPayload |
Yes | Grant from the approval step |
React hook that polls the Session Relay and manages connection state.
const { connect, status, grant, error, deepLinkUrl, reset } = useVanaConnect();status transitions: idle β connecting β waiting β approved | denied | expired | error
Returned when a user approves access:
interface GrantPayload {
grantId: string; // on-chain permission ID
userAddress: string; // user's wallet address
builderAddress: string; // your registered address
scopes: string[]; // approved data scopes
serverAddress?: string; // user's Personal Server
appUserId?: string; // your app's user ID (if provided)
}For full control over individual protocol interactions:
import {
createRequestSigner, // Web3Signed header generation
createSessionRelay, // Session Relay HTTP client
createDataClient, // Data Gateway HTTP client
} from "@opendatalabs/connect/server";See examples/nextjs-starter for a complete working integration with Next.js, including manifest signing, webhook handling, and the full connect-to-data-fetch flow.
MIT