Skip to main content
A Personal Server is the user-controlled environment that stores personal data, responds to authorized data requests from builders, and maintains access logs. It is the only protocol component that sees plaintext data.
A Personal Server is a protocol participant — it registers onchain and can act unattended. The Data Portability Client (e.g. Data Connect desktop app) is a client that configures and controls the Personal Server, but is not itself a protocol participant. Think of it like email: the desktop app is your mail client, the Personal Server is your mail server.

Role in the protocol

The Personal Server is responsible for:
  • Storing user data in plaintext, organized by scope
  • Serving data to builders who hold a valid grant
  • Encrypting data before uploading to storage backends
  • Syncing data across instances (e.g. desktop + cloud)
  • Logging access for auditability
A user can have multiple Personal Server instances (desktop-bundled + cloud) that stay in sync through the storage backend and Data Registry.

Registration

Every Personal Server must be registered onchain via the DataPortabilityServers contract. Registration records the server’s address, public key, and URL. Users then trust a server to act on their behalf. The Gateway provides signature-based registration so the Desktop App can register a server without the user sending an onchain transaction directly:
POST /v1/servers
See Identity for the full registration flow and the DataPortabilityServers contract interface.

API

The Personal Server exposes a versioned REST API under /v1.

Data endpoints

MethodPathDescription
POST/v1/data/{scope}Create a new data document for a scope
GET/v1/dataList available scopes and latest version metadata
GET/v1/data/{scope}Read data for a scope (latest or specific version)
GET/v1/data/{scope}/versionsList available versions (metadata only)
DELETE/v1/data/{scope}Delete data for a scope (user-only)

Grant endpoints

MethodPathDescription
GET/v1/grantsList all grants for this user
POST/v1/grantsCreate a new grant (user-only)
POST/v1/grants/verifyVerify a grant signature

Other endpoints

MethodPathDescription
GET/v1/access-logsGet access log history
GET/healthHealth check (unversioned)

Query parameters for GET /v1/data

ParameterDescription
scopePrefixFilter by scope prefix (e.g. instagram)
limitMaximum number of results
offsetPagination offset

Query parameters for GET /v1/data/{scope}

ParameterDescription
fileIdReturn a specific version by DataRegistry fileId
atReturn the version closest to this ISO 8601 timestamp (closest ≤ at)
If neither parameter is provided, the latest version by collectedAt is returned.

Write flow (POST /v1/data/{scope})

When a data document is written:
  1. Look up schemaId for the scope via the Gateway
  2. Validate the request body against the schema
  3. Generate a collectedAt timestamp (UTC)
  4. Construct the data file envelope with schema URL, version, scope, and timestamp
  5. Store locally in ~/.vana/data/{scope}/{collectedAt}.json
  6. Return 201 Created immediately
  7. Asynchronously: encrypt with the scope key, upload to the storage backend, and register in the DataRegistry

Builder data access

Builders read data from the Personal Server using signed HTTP requests. Every builder request must include an Authorization: Web3Signed header.

Web3Signed authorization

Authorization: Web3Signed <base64url(json)>.<signature>
The payload is a JSON object with keys sorted alphabetically:
{
  "aud": "https://user-abc.server.vana.com",
  "bodyHash": "",
  "exp": 1737500300,
  "grantId": "0x...",
  "iat": 1737500000,
  "method": "GET",
  "uri": "/data/instagram.profile"
}
FieldDescription
audPersonal Server origin (must match)
methodHTTP method of the request
uriRequest path and query string
bodyHashHash of request body (empty string for GET)
iatIssued-at timestamp (Unix seconds)
expExpiration timestamp (Unix seconds)
grantIdOnchain permissionIdrequired for raw data reads
Signing: The JSON is canonicalized (keys sorted alphabetically), then base64url-encoded (no padding). The signature is an EIP-191 signature over the ASCII bytes of the base64url string.

Verification

When the Personal Server receives a builder request, it:
  1. Recovers the signer address from the Authorization header
  2. Verifies the signer is a registered builder onchain
  3. Checks aud matches the server’s own origin
  4. Checks method and uri match the actual request
  5. Validates iat/exp are within the allowed skew window (e.g. 5 minutes)
  6. For data reads: verifies grantId exists onchain and the signer matches the grantee
  7. Confirms the requested scope is within the granted scopes
  8. Logs the access

Access control summary

EndpointWho can call
POST /v1/data/{scope}User only
GET /v1/data, GET /v1/data/{scope}User or builder with valid grant
GET /v1/data/{scope}/versionsUser or builder with valid grant
DELETE /v1/data/{scope}User only
POST /v1/grants, GET /v1/grantsUser only
GET /v1/access-logsUser only

Hosting options

Personal Servers can be hosted in three ways. All are protocol-equivalent — builders interact with the same API regardless of where the server runs.
OptionURL formatOperatorAvailabilityCold start
Desktop-bundledLocal (tunneled via {userId}.server.vana.org)User’s deviceWhile app is openNone
ODL Cloudhttps://server.vana.com/u/{userId}Vana (Sprites.dev)Always on (auto-wakes)~1-2s
Self-hostedhttps://server.alice.comUserUser managesUser manages

Desktop-bundled

The Personal Server runs as an embedded process inside the Data Connect desktop app (Tauri). Data stays on the user’s machine. When the app is closed, the server is unavailable — builder requests return 503. To make a desktop-bundled server reachable from the internet, Vana provides FRP tunneling (Fast Reverse Proxy). The desktop app connects to proxy.server.vana.org and receives a public URL at {userId}.server.vana.org with automatic TLS.

ODL Cloud

ODL Cloud runs Personal Servers on per-user Firecracker MicroVMs via Sprites.dev. Each user gets an isolated VM that:
  • Wakes automatically on incoming requests (~1-2 second cold start)
  • Sleeps when idle (billing stops)
  • Persists data between activations
Delegated signature. When a user enables ODL Cloud, they sign the master key message once. The signature is encrypted and stored in the Sprite. On activation, the Sprite derives the master key in-memory, decrypts data files, and serves requests — no user interaction required. The delegated signature never expires. Users revoke it by disabling ODL Cloud, which deletes the Sprite. Data remains in the storage backend and can be re-provisioned.

Self-hosted

Advanced users can run the Personal Server as a Docker container on their own infrastructure. They control the URL, uptime, and security configuration.

Fallback behavior

If both a desktop-bundled and ODL Cloud server are configured, the onchain registration points to the ODL Cloud URL so builders always have a reachable endpoint. When the desktop app is open, it syncs data to the cloud server through the storage backend.

Sync

When a user has multiple Personal Server instances (e.g. desktop + ODL Cloud), they stay synchronized through the storage backend and Data Registry. See Storage & Encryption — Data sync for the full sync model.

Local data hierarchy

All Personal Server implementations use the same directory layout:
~/.vana/
├── data/                       # Decrypted user data
│   ├── instagram/
│   │   ├── profile/
│   │   │   └── 2026-01-21T10-00-00Z.json
│   │   ├── posts/
│   │   └── likes/
│   ├── chatgpt/
│   │   └── conversations/
│   └── youtube/
│       ├── watch_history/
│       └── subscriptions/
├── logs/                       # Access logs (JSON lines, daily rotation)
│   └── access-2026-01-21.log
├── index.db                    # Local registry index (SQLite)
└── server.json                 # Server configuration
Files are named {YYYY-MM-DDTHH-mm-ssZ}.json using the collectedAt timestamp.