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
bun add @veil/apiClients
The package exports a ready-made client for each surface, plus factories for when you need to attach credentials.
import {
saasApi, // cloud API, cookie credentials included
localApi, // Local API worker
createSaasApiClient,
createLocalApiClient,
} from "@veil/api";| Export | Base URL | Credentials |
|---|---|---|
saasApi | SAAS_API_URL | Sends cookies (credentials: "include") for dashboard sessions |
localApi | LOCAL_API_URL | None — pass a key with the factory |
createSaasApiClient({ apiKey }) | SAAS_API_URL | Cookies plus X-API-Key |
createLocalApiClient({ apiKey }) | LOCAL_API_URL | X-API-Key |
Base URLs
Both defaults are resolved once at import time:
| Client | Variable | Fallback |
|---|---|---|
| Cloud | SAAS_API_URL, then NEXT_PUBLIC_SAAS_API_URL | http://localhost:4000 |
| Local | LOCAL_API_URL, then NEXT_PUBLIC_LOCAL_API_URL | http://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.
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:
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:
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:
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:
import type { CreateSaasProfile, SaasProfileResponse } from "@veil/shared";Next steps
- API reference — the endpoints behind these calls.
- Authentication — which key each client needs.
- Errors — the envelope
getEdenErrorMessageunpacks.