Get Started

Minds Client Library

Call the Builder API with TypeScript types. A Builder API key covers your account. An OAuth access token covers the user who signed in through Minds Connect.

@animocabrands/minds-client-lib is how you call the Builder API from your application. Most builders start with the Minds CLI on their own account, then use this library to ship the same calls with TypeScript types: messaging, waitForReply, SSE events, and account operations. Browser apps that sign users in use the Minds Connect SDK , which wraps this client and refreshes the user's access token before each call.
Use it from a Node backend, or in the browser through Connect. Your UI, branding, and user flows stay yours; the client handles typed HTTP and streaming to api.build. For a Builder API key, complete How to get started and validate with the CLI, then wire the examples below. For user sign-in, register an OAuth client and follow the Connect guide.

Install

Requires Node 22+ for Node apps. Connect loads the same package in the browser.
npm install @animocabrands/minds-client-lib

Authentication

Account and messaging calls accept an OAuth access token or a Builder API key. When both are set, the access token wins and the client sends Authorization: Bearer. Otherwise it sends X-Api-Key. X-Access-Key is deprecated, do not use it. The library does not load .env. Read MINDS_BUILDER_API_KEY in your app (constant BUILDER_API_KEY_ENV) and pass it in. Browser apps should use the Minds Connect SDK . Connect's oauth.client supplies a fresh token before each request. On a backend, after the browser has signed the user in, pass that access token into createMindsClient. A Builder API key acts as your builder account. An access token acts as the signed-in user, and only within the scopes they granted. Builder API key:
import { BUILDER_API_KEY_ENV, createMindsClient } from "@animocabrands/minds-client-lib";
 
const builderApiKey = process.env[BUILDER_API_KEY_ENV];
if (!builderApiKey) throw new Error(`${BUILDER_API_KEY_ENV} is not set`);
 
const client = createMindsClient({ builderApiKey });
OAuth access token:
import { createMindsClient } from "@animocabrands/minds-client-lib";
 
const client = createMindsClient({ accessToken });
client.setAccessToken(nextToken);
client.setAccessToken(null);
setAccessToken(null) or a blank string clears the token. Later calls then use the Builder API key, when one was set at construction. getAccessToken runs before each authenticated HTTP or SSE request. A token it returns wins over a static accessToken for that request. Connect wires this on oauth.client. If the function returns null, the client falls back to the static token or the key.
const client = createMindsClient({
  getAccessToken: async () => accessTokenFromYourSession,
});
Backend, when the browser session is already established:
import { createMindsClient } from "@animocabrands/minds-client-lib";
 
const accessToken = req.headers.authorization?.replace(/^Bearer\s+/i, "");
const minds = await createMindsClient({ accessToken }).listMinds();
ConstantValue
BUILDER_API_KEY_ENVMINDS_BUILDER_API_KEY
BUILDER_API_KEY_HEADERX-Api-Key

Create a client

import { createMindsClient } from "@animocabrands/minds-client-lib";
 
const client = createMindsClient({ builderApiKey: yourBuilderApiKey });
// or: createMindsClient({ accessToken })
createMindsClient({}) is valid for the public Bazaar catalog only (client.bazaar.*). Auth methods throw MindsApiError with code missing_builder_api_key at call time if neither an access token nor a Builder API key was provided, not at construction. The code name is unchanged.

Create a Mind

Mind names are unique. Call checkMindName first (public, no credential) to see whether a name is already taken, then awakenMind (requires a Builder API key or an access token). id selects the type of pre-built Mind to create. The package does not export the catalog. Run minds mind awaken --help in the Minds CLI to list each type with a short description. The same list is in the API reference . If the name is taken or id is not valid, awakenMind throws MindsApiError. The example is a sales Mind named J-Belfort. Sell me this pen.
const check = await client.checkMindName("J-Belfort");
if (!check.isAvailable) throw new Error("name taken");
 
const mind = await client.awakenMind({ name: "J-Belfort", id: "sales" });
await client.ensureConversation("main", mind.mindId);

Minds List

listMinds() returns every Mind on your account (mindId, name, model, species, and related fields).
const minds = await client.listMinds();
const mindId = minds[0]?.mindId;
With an access token, listMinds() calls GET /v1/users/{userId}/minds. userId is the token sub. A Builder API key still calls GET /v1/humans/{humanId}/minds. Pass listMinds({ humanId }) to use the humans path.

Mind details

getMind(mindId) returns full detail - email, wallet address, chain, species, isEnabled, and more:
const detail = await client.getMind(mindId);
console.log(detail.email, detail.walletAddress, detail.chain);

Cognition balance and usage

Per-Mind cognition usage and balance mirror the CLI commands. Minds consume cognition when they reason, run tools, and carry out autonomous tasks, not only in conversation. Both take a mindId (UUID) and an optional AbortSignal. Cognition powers a Mind's reasoning, tool use, and autonomous work. Use getCognitionUsage and getCognitionUsageByTool for usage over time and per tool. Use getCognitionBalance for the remaining cognition balance:
const mindId = (await client.listMinds())[0]?.mindId;
if (!mindId) throw new Error("No Minds on this account");
 
const usage = await client.getCognitionUsage(mindId, { interval: "1d" });
console.log(usage.items); // [{ bucket, value }, ...]
 
const byTool = await client.getCognitionUsageByTool(mindId, { interval: "day" });
console.log(byTool.summary); // [{ tool, callCount, creditsUsed, ... }, ...]
console.log(byTool.timeline); // [{ tool, timeBucket, callCount, creditsUsed }, ...]
 
const balance = await client.getCognitionBalance(mindId);
console.log(balance.cognition);
The two usage methods accept different interval values: getCognitionUsage accepts 1m, 5m, 15m, 1h, 1d, 1w, 1M; getCognitionUsageByTool accepts hour, day, week, month only. Optional startTime and endTime (ISO date-time) bound the window on both.

Mind status

Enable or disable a Mind without deleting it:
await client.updateMindStatus(mindId, { isEnabled: false });
await client.updateMindStatus(mindId, { isEnabled: true });
updateMindStatus returns { msg, result }.

Circles

A Mind's circle is its set of collaborators - humans and other Minds. getCircle returns a CircleMember[] array directly; addCircleMembers and removeCircleMembers return a CircleMutationResult envelope with per-email outcomes and a best-effort summary:
const members = await client.getCircle(mindId);
console.log(members.map((m) => m.email));
 
const added = await client.addCircleMembers(mindId, {
  emails: ["someone@company.com", "the.dude@hellominds.ai"],
  isActive: true,
});
console.log(added.items);
 
const removed = await client.removeCircleMembers(mindId, {
  emails: ["someone@company.com"],
});
console.log(removed.items);
Human emails can be any address; Mind emails end in @hellominds.ai. listCirclesForAccount() is a convenience that calls listMinds() then getCircle() for each Mind in parallel; use it for account-wide overviews rather than hot paths.

Bazaar catalog

The public Bazaar catalog is available as client.bazaar, no API key required. Use Bazaar for ID discovery (skillId, appId), then equip those IDs with the Mind equip methods below. Route shapes are in the API reference Bazaar and Minds tags; the CLI exposes the same catalog via minds bazaar. List items may include equippedCount, how many Minds have equipped that item platform-wide. That is popularity across the platform. Use listEquippedSkills / listEquippedApps for the per-Mind equipped set.
const catalog = createMindsClient({}).bazaar;
 
const skills = await catalog.listSkills({ search: "research", page: 1, pageSize: 25 });
const skill = await catalog.getSkill("skill_abc123");
 
const apps = await catalog.listApps({ search: "notion", tier: "verified" });
const app = await catalog.getApp("app_xyz789");
 
const result = await catalog.collectSearchResults({
  scanMax: 200,
  max: 50,
  sort: "equipped",
  fetchPage: (page, pageSize) => catalog.listSkills({ search: "agent", page, pageSize }),
  getEquippedCount: (s) => s.equippedCount ?? 0,
  getName: (s) => s.name,
  getCreatedAt: (s) => s.createdAt,
});
Apps use appId and appName (not name). collectSearchResults auto-paginates up to scanMax, applies client-side sort/filter, and reports truncated when the scanned set exceeds max. sort: "equipped" orders by equippedCount descending (platform popularity), not per-Mind equip status.

Equip skills and apps

List, equip, or unequip skills and apps on a Mind (requires a Builder API key or an access token). Bodies use { ids: string[] }; mutations return { results: [...] } with skillId / appId, isEquipped, and changed (app results may also include appVersionId and version):
const equippedSkills = await client.listEquippedSkills(mindId);
await client.equipSkills(mindId, { ids: [skill.skillId] });
await client.unequipSkills(mindId, { ids: [skill.skillId] });
 
const equippedApps = await client.listEquippedApps(mindId);
await client.equipApps(mindId, { ids: [app.appId] });
await client.unequipApps(mindId, { ids: [app.appId] });
Discover IDs via client.bazaar first when you need catalog browse. Equipped skills include source: mind for catalog / Mind-authored skills, system for platform skills (e.g. Skill Architect).

Conversations

Bind a stable alias (e.g. main) to a Mind before messaging:
await client.ensureConversation("main", mindId);
ensureConversation is idempotent, if the alias already exists for that Mind, the existing conversation is returned. Lower-level helpers when you manage aliases yourself:
const conversations = await client.listConversations();
const conversation = await client.getConversation("main");
await client.createConversation({ alias: "main", mindId });

Send and history

await client.sendMessage({ alias: "main", messageText: "Hello" });
 
const rows = await client.getHistory("main", { limit: 50 });
// rows[0] is newest. Last fingerprint → next older page (wire `before`).
const cursor = rows.at(-1)?.fingerprint;
const older = await client.getHistory("main", { cursor, limit: 50 });
getHistory returns the full human + Mind transcript, newest-first. Rows use senderType (1 = human, 0 = Mind). The SDK cursor (and deprecated after) is sent as query before - exclusive next older page. The same senderType field appears on SSE events from subscribeEvents / eventsIterator / waitForReply. sendMessage requires alias and messageText. Optional attachments accept outbound objects, prefer a public HTTPS url with fileName and mimeType:
await client.sendMessage({
  alias: "main",
  messageText: "Summarize this PDF in one sentence.",
  attachments: [
    {
      url: "https://www.w3.org/WAI/ER/tests/xhtml/testfiles/resources/pdf/dummy.pdf",
      fileName: "dummy.pdf",
      mimeType: "application/pdf",
      extension: "pdf",
    },
  ],
});
Image URLs use the same shape (mimeType: "image/png", etc.). Mind replies on getHistory, waitForReply, and SSE events may include inbound artifacts, for example:
// One attachment on a Mind reply (PDF; artifact body truncated)
{
  artifactId: "7388b65d-144a-49ff-a4dc-cb7c23ded982",
  slug: "whale_watchtower_skill_artifact_1_0_5",
  logicalType: "document",
  mimeType: "application/pdf",
  extension: "pdf",
  artifact: "JVBERi0xLjQK...",
}
Outbound send uses content for inline payloads; inbound replies use artifact for the same role. slug and logicalType are optional strings, treat values as hints, not a fixed enum. See the API reference for field details. getLatestHistoryFingerprint returns the fingerprint of the newest message. Pass it as afterFingerprint to waitForReply so you only accept replies that arrived after you sent. The first getHistory page (no cursor) is already the newest window - do not pass that fingerprint as cursor expecting newer rows.
Resolve which Mind an alias last talked to:
const mindId = await client.getMindIdForAlias("main");

Wait for a Mind reply

waitForReply listens on the live event stream first, then polls history until a reply arrives or the timeout hits.
const before = await client.getLatestHistoryFingerprint("main");
 
await client.sendMessage({ alias: "main", messageText: "Summarize our plan." });
 
const outcome = await client.waitForReply({
  alias: "main",
  timeoutMs: 180_000,
  afterFingerprint: before,
  sentMessageText: "Summarize our plan.",
});
 
if (!outcome.timedOut) {
  console.log(outcome.reply.messageText);
}
Use isReplyEvent from the package when you filter SSE or history rows yourself.

Live events (SSE)

Subscribe with callbacks:
const sub = client.subscribeEvents({
  alias: "main",
  onEvent: (event) => {
    console.log(event.messageText);
  },
  onError: (err) => console.error(err),
});
 
// later
sub.close();
Or consume the async iterator:
for await (const event of client.eventsIterator({ alias: "main" })) {
  console.log(event.fingerprint, event.messageText);
}
Pass signal on iterator or subscribe options to cancel when your process shuts down.

Errors

Failed HTTP calls throw MindsApiError with status, code, and message. Calling an auth method with neither an access token nor a Builder API key throws missing_builder_api_key at call time. Map 401/403 to a missing, expired, or revoked credential, or to a scope the user did not grant. 429 may include retry guidance.

Methods

AreaMethods
AuthsetAccessToken
AccountlistMinds
MindscheckMindName, awakenMind, getMind, updateMindStatus
CognitiongetCognitionUsage, getCognitionUsageByTool, getCognitionBalance
EquiplistEquippedSkills, equipSkills, unequipSkills, listEquippedApps, equipApps, unequipApps
CirclesgetCircle, addCircleMembers, removeCircleMembers, listCirclesForAccount
Bazaarbazaar.listSkills, bazaar.getSkill, bazaar.listApps, bazaar.getApp, bazaar.collectSearchResults
ConversationscreateConversation, listConversations, getConversation, ensureConversation, getMindIdForAlias
MessagingsendMessage, getHistory, getLatestHistoryFingerprint
ReplieswaitForReply, isReplyEvent, isReplyHistoryRow
EventssubscribeEvents, eventsIterator, parseSseChunk
Types (BuilderMind, Conversation, MessageRecord, MessagingEvent, CognitionBalance, BazaarSkill, BazaarApp, EquippedSkill, EquippedApp, CircleMember, CircleMutationResult, CognitionUsageResponse, CognitionUsageByToolResponse, UpdateMindStatusBody, UpdateMindStatusResult, CheckMindNameResult, AwakenMindResult, …), parseUserIdFromAccessToken, and parseHumanIdFromBuilderApiKey are exported from the package entry.