SDK.
@pambase/sdk is the TypeScript client for the REST API. It is a thin, typed wrapper: one PAMbaseApp instance per connection, plus standalone helpers for the OAuth handshake and webhook verification. Every method maps to an endpoint you can read about on the API page.
Install
pnpm add @pambase/sdk
Construct
Server-side client. Create one instance per connection token.
import { PAMbaseApp } from "@pambase/sdk";const pam = new PAMbaseApp({baseUrl: "https://api.pambase.io", // http://localhost:4000 in devconnectionToken: storedToken, // from exchangeCodeForToken(...)});
| Option | Type | Notes |
|---|---|---|
baseUrl | string | API origin |
connectionToken | string | Sent as Authorization: Bearer |
fetchImpl? | typeof fetch | Override the fetch implementation (tests, edge runtimes) |
retry? | RetryConfig | Override retry behavior (see Retry below) |
The connection token grants access to a user's memory. Keep it on your server; never ship it to a browser. The same goes for client_secret in the OAuth helpers.
Methods
Identity
| Method | Returns | Endpoint |
|---|---|---|
getIdentityBrief() | { brief, ai_name, generated_at, source_memory_count, cached } | GET /v1/identity/brief |
systemPrompt() | string | Convenience: brief formatted as a system prompt |
getConnection() | Connection & { ai, app } | GET /v1/connection |
const { brief, ai_name } = await pam.getIdentityBrief();const systemPrompt = await pam.systemPrompt(); // ready to prepend to your LLM call// Need the raw profile (display name + tone note)? It's on the connection:const { ai } = await pam.getConnection(); // ai.name, ai.personality?.description
The profile is just a display name and an optional one-line tone note — read it from getConnection() (the ai field). Apps bring their own persona and model — see Identity.
Memory & signals
| Method | Returns | Endpoint |
|---|---|---|
remember(input | input[]) | { accepted, memoryIds } | POST /v1/memories |
recall({ query, scope?, limit? }) | { memories: SearchedMemory[] } | GET /v1/memories?query= |
listMemories({ scope?, limit?, cursor? }) | { memories, next_cursor? } | GET /v1/memories |
iterateMemories({ scope?, pageSize? }) | AsyncIterable<Memory> | (paged GET /v1/memories) |
emitSignal({ type, payload? }) | { accepted } | POST /v1/signals |
getContext({ intent, query?, limit? }) | ContextBundle | GET /v1/context |
// Write natural-language memory (any app; pick a scope from the taxonomy).await pam.remember({ content: "Ran 5k in 28:10.", kind: "event", scope: "fitness.running" });// Recall the relevant memories; bring your LLM to interpret them.const { memories } = await pam.recall({ query: "marathon training", scope: "fitness", limit: 5 });// Emit an ephemeral signal for subscribers (not stored as memory).await pam.emitSignal({ type: "workout.completed", payload: { activity: "run" } });
See Remember & recall, Signals, and the data model for response shapes.
Hosted chat (Gateway)
| Method | Returns | Endpoint |
|---|---|---|
chat({ intent, userMessage?, appContext?, ephemeral? }) | GatewayChatResponse | POST /v1/gateway/chat |
chatStream(request, signal?) | AsyncGenerator<ChatStreamEvent> | POST /v1/gateway/chat/stream |
const res = await pam.chat({ intent: "coach", userMessage: "How did my week go?" });console.log(res.reply, res.toneTags);// Streaming — discriminate on event.type:for await (const ev of pam.chatStream({ intent: "coach", userMessage: "Recap?" })) {if (ev.type === "delta") process.stdout.write(ev.text);else if (ev.type === "memory_recorded") console.log("\nrecorded", ev.candidate.scope);else if (ev.type === "done") console.log("\n", ev.usage.model, ev.usage.tokensOut);else if (ev.type === "error") throw new Error(ev.message);}
Set ephemeral: true to skip recording memory candidates for the turn. See Gateway and Chat.
Scheduled triggers
| Method | Returns | Endpoint |
|---|---|---|
schedule({ fireAt, kind, payload? }) | { id, fireAt } | POST /v1/schedule |
listSchedules() | { triggers: [{ id, fireAt, kind, status, payload }] } | GET /v1/schedule |
cancelSchedule(id) | { ok: true } | DELETE /v1/schedule/:id |
const trg = await pam.schedule({fireAt: new Date(Date.now() + 86_400_000), // accepts Date or ISO stringkind: "morning-checkin",payload: { tip: "Easy run today" },});await pam.cancelSchedule(trg.id);
When a trigger fires, PAMbase delivers a schedule.fired webhook — see Webhooks.
Discovery
| Method | Returns | Endpoint |
|---|---|---|
getCatalog() | PAMbaseCatalog | GET /v1/catalog |
listUserScopes() | { scopes: [{ scope, count, standard }] } | GET /v1/scopes |
Render the canonical catalog on the Catalog page, and discover what a given user actually has at runtime. See Discovery.
Standalone helpers
Pure functions for the OAuth handshake and webhook verification — no client instance required.
import {buildConnectUrl,exchangeCodeForToken,verifyWebhookSignature,} from "@pambase/sdk";// 1. Send the user to the Hub to consent:const url = buildConnectUrl({hubBaseUrl: "https://pambase.io",appSlug: "my-app",redirectUri: "https://my-app.com/callback",state: csrfToken,scopes: ["identity:read", "memory:read:fitness", "memory:write:fitness"],});// 2. On the callback, exchange the code for a token:const { connection_token, ai_id, scopes } = await exchangeCodeForToken({apiBaseUrl: "https://api.pambase.io",code,clientId: process.env.PAMBASE_CLIENT_ID!,clientSecret: process.env.PAMBASE_CLIENT_SECRET!,});// 3. Verify inbound webhooks:const ok = await verifyWebhookSignature({payload: rawBody,signature: req.headers["x-pambase-signature"],secret: process.env.PAMBASE_WEBHOOK_SECRET!,toleranceSeconds: 300,});
Full flow in Connect flow; signing details in Webhooks.
Errors
Every failure throws a subclass of PAMbaseError (with .code, .status, .message). Catch the specific class you care about. The full mapping lives on the Errors page.
| Class | Status | Extra |
|---|---|---|
PAMbaseError | — | Base class |
ValidationError | 400 | Zod issues in .details |
UnauthorizedError | 401 | Re-run the OAuth connect flow |
ScopeDeniedError | 403 | .scope |
NotFoundError | 404 | — |
RateLimitError | 429 | .retryAfterMs |
ServerError | ≥500 | Retried automatically |
NetworkError | — | Transport failure; retried automatically |
import {UnauthorizedError, ScopeDeniedError, RateLimitError, classifyError,} from "@pambase/sdk";try {await pam.remember({ content: "Ran 5k this morning.", kind: "event", scope: "fitness" });} catch (err) {if (err instanceof UnauthorizedError) {redirectToConnect(); // token missing/expired/revoked} else if (err instanceof ScopeDeniedError) {console.warn("Need scope:", err.scope); // request it in your manifest} else if (err instanceof RateLimitError) {await sleep(err.retryAfterMs); // then retry} else {throw classifyError(err);}}
Retry
Every request auto-retries NetworkError, RateLimitError, and 5xx responses by default. Tune it via the constructor's retry option, or wrap your own work with withRetry.
import { withRetry, type RetryConfig } from "@pambase/sdk";const retry: RetryConfig = {maxAttempts: 3, // defaultinitialDelayMs: 200, // defaultmaxDelayMs: 4000, // defaultjitter: 0.25, // defaultshouldRetry: (err) => true, // optional custom predicate};const pam = new PAMbaseApp({ baseUrl, connectionToken, retry });await withRetry(() => pam.getIdentityBrief(), retry);
Pagination
List endpoints return Paginated<T> = { items, next_cursor? }. Use iterate(fetcher) to stream pages lazily, or collect(fetcher, max = 1000) to gather them. iterateMemories() is a ready-made wrapper for memory.
import { collect } from "@pambase/sdk";const all = await collect((cursor) => pam.listMemories({ scope: "fitness", cursor }),500, // safety cap);
Standard-name constants
Typo-safe constants for the standard vocabulary are exported. Any string is still accepted — these just give you autocomplete and the standard flag in discovery results.
import {STANDARD_SCOPE_NAMESPACES,MEMORY_KIND_NAMES,STANDARD_INTENT_NAMES,} from "@pambase/sdk";import type {StandardScopeNamespace, MemoryKindName, StandardIntent,} from "@pambase/sdk";
Types
All response types are exported from @pambase/sdk. Their shapes are documented on the data model page.
import type {AIIdentity, Memory, SearchedMemory, ContextBundle,IdentityBrief, Connection, ConnectedApp,GatewayChatRequest, GatewayChatResponse, ChatStreamEvent,PAMbaseCatalog, Paginated,} from "@pambase/sdk";