Fetch transport
Mount Ownfold's portable Web Request handler, configure its validated browser transport, and secure origins, credentials, limits, and errors.
@ownfold/fetch is the canonical HTTP protocol used by the Next.js, Hono, Fastify, Node, Elysia,
and TanStack Start adapters. It uses standard Request and Response objects and has no framework
or database dependency.
Choose the database and identity independently in the framework recipe builder. The fetch handler works with every combination and requires no frontend.
Install
pnpm add @ownfold/browser @ownfold/fetch @ownfold/server
Server handler
import { createVaultFetchHandler } from "@ownfold/fetch"
export const handleVaultRequest = createVaultFetchHandler({
server: vaultServer,
basePath: "/api/ownfold",
isOriginAllowed: ({ request }) => {
if (request.headers.get("sec-fetch-site") === "cross-site") return false
const origin = request.headers.get("origin")
return origin === null || origin === "https://app.example.com"
},
})
serverVaultServer
VaultServerbasePath?string
stringisOriginAllowed?(input: { readonly request: Request }) => boolean | Promise<boolean>
(input: { readonly request: Request }) => boolean | Promise<boolean>Pass the original Request through unchanged so the server’s authentication resolver receives the
host application’s cookies and authorization headers. Framework adapters should delegate all
Ownfold paths to this handler instead of recreating validation or error mapping.
Origin checks apply to state-changing requests. Configure an explicit production policy whenever the endpoint can be reached cross-origin; CORS response headers alone do not prevent forged writes.
Browser transport
import { createVaultClient } from "@ownfold/browser"
import { createFetchVaultTransport } from "@ownfold/fetch"
export const vault = createVaultClient({
transport: createFetchVaultTransport({
baseURL: "/api/ownfold",
credentials: "same-origin",
}),
})
baseURL?string
stringcredentials?RequestCredentials
RequestCredentialsfetch?typeof globalThis.fetch
typeof globalThis.fetchheaders?HeadersInit | (() => HeadersInit | Promise<HeadersInit>)
HeadersInit | (() => HeadersInit | Promise<HeadersInit>)Use credentials: "include" only for an intentional cross-origin cookie deployment with a strict
origin allowlist and correctly scoped cookies. A headers callback can obtain a fresh host-issued
access token for each request:
const transport = createFetchVaultTransport({
baseURL: "https://api.example.com/api/ownfold",
credentials: "omit",
headers: async () => ({ authorization: `Bearer ${await getAccessToken()}` }),
})
Recovery secrets and root keys must never be placed in headers.
Protocol guarantees
The default base path is /api/ownfold. JSON request and response bodies are bounded to 65,536
bytes. Requests with invalid JSON, the wrong content type, unsupported methods, unknown routes, or
malformed inputs fail before reaching storage.
Successful responses use:
{ ok: true, data: value }
Failures use:
{ ok: false, error: { code, message } }
The browser transport validates returned vault, device, pairing, and rotation values. A malformed
success response becomes INVALID_VAULT_RESPONSE, and local key state remains unchanged.
Route surface
| Path | Methods | Purpose |
|---|---|---|
/vault |
GET, POST |
Read or create vault metadata. |
/recovery/verify |
POST |
Mark locally verified recovery setup. |
/recovery/replace |
POST |
Commit a replacement Recovery Kit ID. |
/devices |
GET, POST |
List or register devices. |
/devices/touch, /devices/revoke |
POST |
Activity and revocation. |
/pairings |
POST |
Create a pairing offer. |
/pairings/status |
GET |
Poll an offer by request ID. |
/pairings/approve, /pairings/cancel |
POST |
Resolve pairing. |
/rotation |
GET, POST |
Read or begin rotation. |
/rotation/progress, /rotation/complete |
POST |
Checkpoint or atomically cut over. |
No route accepts a user ID. Ownership always comes from the authenticated request.
Custom fetch implementation
Tests, desktop shells, and instrumented runtimes can inject fetch:
const transport = createFetchVaultTransport({
fetch: async (input, init) => {
assertNoPlaintext(init?.body)
return applicationFetch(input, init)
},
})
Instrumentation must not log request bodies. They contain encrypted envelopes and metadata even though they do not contain plaintext.
Error behavior
Network failures return TransportError with VAULT_NETWORK_FAILED. Non-success HTTP responses
preserve the server’s safe error code when the response shape is valid. Invalid or oversized
responses return INVALID_VAULT_RESPONSE.
Retry only after reconciling current state. Creation uses stable identities and storage-level idempotency, but revision-checked mutations require a fresh read after a conflict.
Production checklist
- Mount the entire handler under one exact base path.
- Preserve the original request headers and cookies.
- Configure a strict origin policy for browser writes.
- Keep the 65,536-byte protocol limit at the outer proxy and framework layers.
- Do not enable body logging, plaintext analytics, or request replay capture.
- Use HTTPS in production.
- Return cache-prevention headers for authenticated lifecycle responses at the platform edge.
- Test malformed JSON, oversized bodies, absent sessions, stale revisions, and offline recovery.
Troubleshooting
Every operation returns unauthorized
The framework bridge may be constructing a new request without cookies or authorization headers. Pass the original headers and confirm the browser credential mode matches the host session.
ORIGIN_NOT_ALLOWED
The request origin does not match the configured policy. Compare normalized scheme, host, and port; do not solve this by accepting every origin in production.
INVALID_VAULT_RESPONSE
A proxy, error page, or incompatible server returned a body outside the Ownfold protocol. Inspect status and content type server-side while keeping the response body out of user telemetry.