The official TypeScript SDK, @totemstudioai/sdk, covers every endpoint of the read-only Beta API. It runs on your server with Node.js 22.12 or later. A team administrator creates its key in Settings → Developers; public API access is included from Pro.
Install the TypeScript SDK
Install the release tarball from the official GitHub repository. It contains the built package, so it needs no build step and works with install scripts disabled. Download it with its checksum, check it, then install the file you checked:
curl -fLO https://github.com/kiketordera/totem-studio-typescript/releases/download/v0.1.0-beta.2/totemstudioai-sdk-0.1.0-beta.2.tgz
curl -fLO https://github.com/kiketordera/totem-studio-typescript/releases/download/v0.1.0-beta.2/totemstudioai-sdk-0.1.0-beta.2.tgz.sha256
shasum -a 256 -c totemstudioai-sdk-0.1.0-beta.2.tgz.sha256
npm install ./totemstudioai-sdk-0.1.0-beta.2.tgzThe checksum shows that your download is complete and unchanged; it comes from the same release page as the tarball, so it does not prove who built the file. npm records the dependency as a file: path to that tarball, so keep the tarball where you installed it from. You can also install the Git tag with npm install github:kiketordera/totem-studio-typescript#v0.1.0-beta.2, which builds the package during installation and therefore needs install scripts enabled.
Either way the package is imported as @totemstudioai/sdk. It is not published on the npm registry yet, so a package with that name on npm is not Totem's: never give it your key, and install the SDK only in one of the two ways above. Keep your key in your server's secret store and provide it as TOTEM_API_KEY. Never bundle this client or its key into a browser or mobile app.
Make a typed request
The examples on this page are ES modules and use await at the top level. In a CommonJS project, put them inside an async function.
import { TotemClient } from '@totemstudioai/sdk'
const apiKey = process.env.TOTEM_API_KEY
if (!apiKey) throw new Error('Set TOTEM_API_KEY on your server')
const client = new TotemClient({ apiKey })
const integration = await client.me.get()
const teams = await client.teams.list()
console.log(integration.id, teams.data.map(team => team.id))For a key that reads several teams, select one with the client's team option. You can also pass team per request on the routes that read one team's collection:
const team = process.env.TOTEM_TEAM_ID
if (!team) throw new Error('Set TOTEM_TEAM_ID on your server')
const client = new TotemClient({ apiKey, team })
const page = await client.leads.list({ limit: 100 })
const matching = await client.leads.search({ email: 'alex.rivera@example.com' })
console.log(page.data.length, matching.data.length)A route that reads one record by its id, such as client.leads.get(id), takes the team from the id and has no team option: compare the record's team with the one you expect. The client supplies Authorization and Totem-Team, encodes filters and returns typed response objects. Field names stay the same as the API's JSON. Fields with a fixed set of values are typed so that a value added later still compiles, because enums can grow.
Read every page with the SDK
Use items to read a list without managing its page cursors:
for await (const lead of client.items('leads.list', { query: { limit: 100 } })) {
console.log(lead.id, lead.version)
}Use pages when you need the whole page envelope. It stops once has_more is false; it is not a scheduler. timeoutMs applies to each page; pass signal: AbortSignal.timeout(ms) to bound a whole traversal.
Read the changes feed with pages or client.sync.changes({ cursor, limit: 100 }), never with items: apply each page before saving its next_cursor, including on an empty page with has_more: false. A cursor that is undefined or null starts from the beginning. Keep one cursor per team and resume polling every 1 to 5 minutes. Use isFeedRemoval(item) to tell a removal from a record's current state, and apply an item only when its version is greater than the one you hold. Keep a copy in sync explains removals, versions and full resync; the SDK does not store your data or cursors for you.
SDK errors and response metadata
import { TotemApiError, TotemError } from '@totemstudioai/sdk'
try {
const response = await client.requestWithResponse('me.get')
console.log(response.requestId, response.data.id)
} catch (error) {
if (error instanceof TotemApiError) {
console.error(error.status, error.code, error.action, error.param, error.requestId)
} else if (error instanceof TotemError) {
console.error(error.name)
}
throw error
}Every error the SDK raises for a request it sent extends TotemError:
TotemApiErrorfor an error the API answered. It exposes the HTTP status, the API errorcode, its recommendedaction, theparamat fault,title, the request id andretryAfterMswhen supplied.TotemProtocolErrorfor a response the contract does not allow, such as a successful answer whose body is not JSON, a list page withoutdata, or a missing or repeated cursor duringpages()oritems().TotemConnectionErrorfor a network failure that retries could not recover, andTotemTimeoutErrorfor the request's deadline.
Invalid options or arguments, such as an empty apiKey or team, throw a plain TypeError before any request is sent, and your own signal's abort reason is rethrown unchanged.
Branch on action, the SDK's grouping of error codes: fix_request, check_credentials, resync, wait_capture, drop_team, keep_and_retry, retry_after, backoff or contact_support. The exported errorCatalogue maps every documented code to its action, and the error catalogue explains each code; handle an unknown code by its HTTP status. On the changes feed, action === 'resync' (resync_required or sync_cursor_expired) means a full resync; capture_not_ready means keep your data and cursor and try again later; team_not_accessible means read GET /v1/teams again and drop the team only if it is no longer listed. Without the SDK, branch on the response's code. Keep the request id for support.
The client makes bounded, jittered retries for temporary failures and respects Retry-After. A wait beyond maxRetryDelayMs, or beyond the time left before the request's deadline, is returned to your code at once, rather than shortened. Use timeoutMs, maxRetries, maxRetryDelayMs and signal to fit your job's deadline. Authentication, authorization and invalid-request errors need a correction, not a retry loop.
Verify your SDK integration
Inject a fetch implementation through new TotemClient({ apiKey, fetch }) for deterministic HTTP tests. Test your receiver's version handling, removals and cursor persistence separately, then run real requests with your own key. Testing and go-live lists the behavior to verify; a mocked response does not prove a live key or server works.