VeilBrowserDocs

SDK

Typed TypeScript clients for the cloud and Local APIs via @veil/api.

@veil/api exposes Eden Treaty clients built from the API server types. Routes, request bodies and response shapes are inferred from the running code, so a change to an endpoint surfaces as a TypeScript error rather than a runtime surprise.

Installation

terminal
bun add @veil/api

Clients

The package exports a ready-made client for each surface, plus factories for when you need to attach credentials.

clients.ts
import {
  saasApi,          // cloud API, cookie credentials included
  localApi,         // Local API worker
  createSaasApiClient,
  createLocalApiClient,
} from "@veil/api";
ExportBase URLCredentials
saasApiSAAS_API_URLSends cookies (credentials: "include") for dashboard sessions
localApiLOCAL_API_URLNone — pass a key with the factory
createSaasApiClient({ apiKey })SAAS_API_URLCookies plus X-API-Key
createLocalApiClient({ apiKey })LOCAL_API_URLX-API-Key

Base URLs

Both defaults are resolved once at import time:

ClientVariableFallback
CloudSAAS_API_URL, then NEXT_PUBLIC_SAAS_API_URLhttp://localhost:4000
LocalLOCAL_API_URL, then NEXT_PUBLIC_LOCAL_API_URLhttp://localhost:3000

The resolved values are also exported as SAAS_API_URL, LOCAL_API_URL and a config object, which is handy for logging what a process actually connected to.

Making calls

Every call returns { data, error } — Eden never throws on a non-2xx response, so handle both branches.

profiles.ts
import { saasApi } from "@veil/api";

const { data, error } = await saasApi.v1.profiles.get();

if (error) {
  // error.status is the HTTP status, error.value the parsed body
  throw new Error(`Failed to list profiles: ${error.status}`);
}

for (const profile of data.data.items) {
  console.log(profile.id, profile.name);
}

Path segments map to properties and the HTTP verb is the final call. Dynamic segments are function calls:

launch.ts
import { createLocalApiClient } from "@veil/api";

const worker = createLocalApiClient({ apiKey: process.env.WORKER_API_KEY });

const { data, error } = await worker.sessions.profile.post({
  profileId: "8f3c1b20-4e1a-4f57-9c22-6d0b7c9a1f44",
});

if (error) throw new Error("Launch refused");

const cdpUrl = data.data.webSocketDebuggerUrl;

// …drive the browser, then clean up
await worker.sessions({ id: data.data.id }).delete();

Error messages

Eden surfaces errors in several shapes depending on where they came from: a plain string, a { message } object, or the VeilBrowser structured envelope. getEdenErrorMessage flattens all of them to a single string with a fallback:

errors.ts
import { getEdenErrorMessage, saasApi } from "@veil/api";

const { data, error } = await saasApi.v1.profiles.post({
  name: "marketing-us",
  os: "win",
  browserVersion: 147,
});

if (error) {
  toast.error(getEdenErrorMessage(error, "Could not create the profile"));
  return;
}

For branching logic, read error.value.error.code and switch on the error code rather than the message text.

Types

SaasApp and LocalApp are the server application types the clients are built from. Export them when you need to type a wrapper of your own:

types.ts
import type { LocalApp, SaasApp } from "@veil/api";
import type { Treaty } from "@elysiajs/eden";

function makeWorkerPool(clients: Treaty.Create<LocalApp>[]) {
  // …
}

Domain types — profile shapes, proxy configuration, enums — live in @veil/shared and are inferred from the same Zod schemas the API validates against:

shared-types.ts
import type { CreateSaasProfile, SaasProfileResponse } from "@veil/shared";

Next steps

Was this page helpful?

On this page