Skip to content
Ownfold
Esc
navigateopen⌘Jpreview
On this page

Transport API reference

Complete reference for Fetch and tRPC server handlers, browser transports, bounded JSON parsing, protocol responses, options, errors, and custom transport boundaries.

Transports move vault lifecycle metadata between VaultClient and the authenticated VaultServer. They never carry application record plaintext, Recovery Kit contents, secrets, or raw keys.

Fetch transport

pnpm add @ownfold/fetch@beta

@ownfold/fetch works in runtimes with standard Request, Response, Headers, and fetch.

Server handler

import { createVaultFetchHandler } from "@ownfold/fetch"

export const handleOwnfold = createVaultFetchHandler({
  server: vaultServer,
  basePath: "/api/ownfold",
  isOriginAllowed: ({ request }) =>
    new URL(request.url).origin === process.env.PUBLIC_APP_ORIGIN,
})
PropType
serverVaultServer
TypeVaultServer
basePath?string
Typestring
isOriginAllowed?(input: { readonly request: Request }) => boolean | Promise<boolean>
Type(input: { readonly request: Request }) => boolean | Promise<boolean>
Option Default Contract
server Required The configured authenticated VaultServer.
basePath /api/ownfold Exact mount path used for operation routing. Keep client and server equal.
isOriginAllowed Same-origin checks remain framework-owned Return true only for origins the host trusts to send credentialed mutations.

createVaultFetchHandler returns (request: Request) => Promise<Response>. It handles GET and POST lifecycle operations below basePath, rejects unknown paths and methods, parses strict input, and maps typed errors to remote-safe JSON.

Browser client transport

import { createFetchVaultTransport } from "@ownfold/fetch"

const transport = createFetchVaultTransport({
  baseURL: "/api/ownfold",
  credentials: "same-origin",
  headers: async () => ({ "x-csrf-token": await csrfToken() }),
})
PropType
baseURL?string
Typestring
credentials?RequestCredentials
TypeRequestCredentials
fetch?typeof globalThis.fetch
Typetypeof globalThis.fetch
headers?HeadersInit | (() => HeadersInit | Promise<HeadersInit>)
TypeHeadersInit | (() => HeadersInit | Promise<HeadersInit>)
Option Default Use
baseURL /api/ownfold Relative or absolute handler URL.
credentials same-origin Browser credential policy. Use include only with a deliberate cross-origin session design.
fetch globalThis.fetch Runtime implementation or a typed test double.
headers None Static headers or an async provider evaluated for each request.

The returned object implements both VaultTransport and VaultRotationTransport. It validates the response envelope before returning a result. A successful HTTP status with malformed JSON is still an error.

Protocol constants

Export Value Meaning
VAULT_HTTP_DEFAULT_BASE_PATH /api/ownfold Default handler and transport mount.
VAULT_HTTP_MAX_BODY_BYTES 65536 Default maximum JSON body size in bytes.

Bounded JSON utilities

readBoundedJsonRequest(request, options?) and readBoundedJsonResponse(response, options?) return Result<unknown, InvalidInputError>. They check declared and streamed size, decode UTF-8, and parse JSON without casting it to an application type.

PropType
maxBodyBytes?number
Typenumber

Use them when building an adjacent custom endpoint with the same body-size behavior. After reading, parse the returned unknown through an Ownfold namespace or Standard Schema.

HTTP response envelope

PropType
oktrue
Typetrue

Success responses contain { ok: true, data }. Failure responses contain only the stable code and remote-safe message. Do not attach stack traces, driver causes, session values, or request bodies.

Route surface

The handler exposes lifecycle coordination operations corresponding to the server methods:

Family Operations
Vault get, create, mark recovery verified, replace Recovery Kit
Devices list, register, touch, revoke
Pairing create, read, approve, cancel
Rotation read, begin, update progress, complete

Application encrypted-record CRUD is deliberately absent. Build application routes that authorize the row and call validateEncryptedRecordWrite before persistence.

Origin and CSRF policy

The Fetch handler accepts an async isOriginAllowed hook because trusted origins are application configuration. Framework bridges with native Node requests require an explicit origin option. A valid session cookie is not, by itself, a cross-site request defense.

For state-changing routes:

  • allow only exact configured origins;
  • reject missing or invalid origin evidence according to the host’s non-browser policy;
  • keep cookies Secure, HttpOnly, and appropriately SameSite; and
  • add the host’s CSRF token strategy when credential policy requires it.

Error mapping

Failure Transport result
Network failure or aborted request TransportError with VAULT_NETWORK_FAILED
Malformed or oversized response TransportError/input error with a stable invalid-response code
Typed server rejection The corresponding Ownfold error reconstructed from its stable code
Unknown server failure Generic transport/server error; no raw body is trusted

Retry reads and operations with stable idempotency identities. Do not automatically replay a mutation merely because the connection ended before its response arrived; reload remote state first.

tRPC

pnpm add @ownfold/trpc@beta @trpc/server

The root entry point builds the server router. The /client entry point adapts a typed tRPC client to the same browser transport contracts.

createVaultTRPCRouter(options)

import { createVaultTRPCRouter } from "@ownfold/trpc"

export const ownfoldRouter = createVaultTRPCRouter({
  server: vaultServer,
})
PropType
serverVaultServer
TypeVaultServer

The context must contain a standard authenticated Request. The router does not accept user ID in procedure input.

PropType
requestRequest
TypeRequest

Procedures

Query Output
getVault `VaultMetadata
listDevices readonly DeviceSummary[]
getPairing `PairingRequest
getRotation `RotationState
Mutation Input family
createVault, markRecoveryVerified, replaceRecoveryKit Strict remote vault inputs
registerDevice, touchDevice, revokeDevice Strict remote device inputs
createPairing, approvePairing, cancelPairing Strict pairing inputs
beginRotation, updateRotationProgress, completeRotation Strict remote rotation inputs

The router uses the same server pipeline and error codes as the Fetch handler. tRPC errors are an integration boundary, not a different vault behavior.

trpcVaultTransport(client)

import { trpcVaultTransport } from "@ownfold/trpc/client"

const transport = trpcVaultTransport(trpcClient.ownfold)
PropType
getVault{ query(): Promise<RemoteVaultState | null> }
Type{ query(): Promise<RemoteVaultState | null> }
createVault{ mutate(input: CreateRemoteVaultInput): Promise<RemoteVaultState> }
Type{ mutate(input: CreateRemoteVaultInput): Promise<RemoteVaultState> }
markRecoveryVerified{ mutate(input: MarkRecoveryVerifiedInput): Promise<RemoteVaultState> }
Type{ mutate(input: MarkRecoveryVerifiedInput): Promise<RemoteVaultState> }
replaceRecoveryKit{ mutate(input: ReplaceRemoteRecoveryKitInput): Promise<RemoteVaultState> }
Type{ mutate(input: ReplaceRemoteRecoveryKitInput): Promise<RemoteVaultState> }
listDevices{ query(): Promise<readonly DeviceSummary[]> }
Type{ query(): Promise<readonly DeviceSummary[]> }
registerDevice{ mutate(input: RegisterRemoteDeviceInput): Promise<DeviceSummary> }
Type{ mutate(input: RegisterRemoteDeviceInput): Promise<DeviceSummary> }
touchDevice{ mutate(input: TouchRemoteDeviceInput): Promise<DeviceSummary> }
Type{ mutate(input: TouchRemoteDeviceInput): Promise<DeviceSummary> }
revokeDevice{ mutate(input: RevokeRemoteDeviceInput): Promise<DeviceSummary> }
Type{ mutate(input: RevokeRemoteDeviceInput): Promise<DeviceSummary> }
createPairing{ mutate(input: CreatePairingRequestInput): Promise<PairingRequest> }
Type{ mutate(input: CreatePairingRequestInput): Promise<PairingRequest> }
getPairing{ query(input: GetPairingRequestInput): Promise<PairingRequest | null> }
Type{ query(input: GetPairingRequestInput): Promise<PairingRequest | null> }
approvePairing{ mutate(input: ApprovePairingRequestInput): Promise<PairingRequest> }
Type{ mutate(input: ApprovePairingRequestInput): Promise<PairingRequest> }
cancelPairing{ mutate(input: CancelPairingRequestInput): Promise<PairingRequest> }
Type{ mutate(input: CancelPairingRequestInput): Promise<PairingRequest> }
getRotation{ query(): Promise<RotationState | null> }
Type{ query(): Promise<RotationState | null> }
beginRotation{ mutate(input: BeginRemoteRotationInput): Promise<RotationState> }
Type{ mutate(input: BeginRemoteRotationInput): Promise<RotationState> }
updateRotationProgress{ mutate(input: UpdateRemoteRotationProgressInput): Promise<RotationState> }
Type{ mutate(input: UpdateRemoteRotationProgressInput): Promise<RotationState> }
completeRotation{ mutate(input: CompleteRemoteRotationInput): Promise<CompletedRemoteRotation> }
Type{ mutate(input: CompleteRemoteRotationInput): Promise<CompletedRemoteRotation> }

The adapter requires only the query/mutation methods in VaultTRPCClient; it does not depend on a specific React Query binding. Procedure failures become TransportError with stable safe details.

Custom transport

Implement VaultTransport & VaultRotationTransport when neither Fetch nor tRPC matches the host. Use the exact inputs and outputs in Adapter and transport contracts, validate all untrusted responses, preserve error codes, and never add userId to client-callable inputs.

See the complete export index for every transport export.

Last updated on August 4, 2026

Was this page helpful?