Get Started

Minds Connect SDK

Connect your application to Hello Minds. Your users sign in with their Minds account, or create one on the spot, and your app can see, message, and work with their Minds.

The Minds Connect SDK is the TypeScript package that makes that connection. Add @animocabrands/minds-connect to your web app. Your users sign in on Hello Minds, including someone creating an account for the first time. Login and consent stay there. When they come back, oauth.client is the Minds Client Library , ready to list their Minds, send messages, and do the rest of what they allowed. Connect keeps that session fresh before each call.Your screens stay yours. Connect handles the redirect, the saved session, and sign-out from your app.
Register an OAuth client in the Builder console, copy the client ID into the SDK, and add a callback route at the redirect URI you registered. The examples below are TypeScript.

Install

Types ship with the package. It runs in the browser. Load it from Vite, webpack, or another bundler. On the Next.js App Router, put the React bindings in a "use client" boundary.
npm install @animocabrands/minds-connect
ImportUse when
@animocabrands/minds-connectTypeScript without React
@animocabrands/minds-connect/reactReact. Needs react and react-dom 18 or newer as an optional peer

Register an OAuth client

Create a client on the OAuth Client tab. Copy the client ID into clientId. The ID is public. There is no client secret to store. The redirect URI is the page in your app that finishes sign-in. The authorized origin is the site allowed to start it. Both have to match the values you register, including scheme, host, and port. Keep the user in the same browser tab. Connect stores a one-time check in sessionStorage for that tab. A new tab, a popup, or a bookmarked callback URL cannot finish sign-in. The tab lists Available scopes. That is this client's allowlist. You cannot edit it there. Pass scopes from that list with MindsScope. Sign-in fails if you ask for one that is not on it. MindsScope.Email is the signed-in user's account email, and it is on the allowlist. MindsScope.MindsEmail is a different scope: the address of one of their Minds.

React

Wrap the router once with MindsConnect, so the home page and the callback share one client. Options are read when the provider mounts. Reload the page to change them. Put LoginCallback on the same path as redirectUri. signIn() sends the user to Hello Minds. After they return, call the Builder API on oauth.client.
import { BrowserRouter, Route, Routes, useNavigate } from "react-router-dom";
import {
  LoginCallback,
  MindsConnect,
  MindsScope,
  useMindsConnect,
  type MindsOAuthOptions,
} from "@animocabrands/minds-connect/react";
 
const opts: MindsOAuthOptions = {
  clientId: "YOUR_CLIENT_ID",
  redirectUri: `${window.location.origin}/callback`,
  scopes: [MindsScope.Email, MindsScope.MindsList],
};
 
export function Root() {
  return (
    <BrowserRouter>
      <MindsConnect opts={opts}>
        <Routes>
          <Route path="/" element={<Home />} />
          <Route path="/callback" element={<Callback />} />
        </Routes>
      </MindsConnect>
    </BrowserRouter>
  );
}
 
function Home() {
  const { isInitialized, isAuthenticated, signIn, signOut, oauth } = useMindsConnect();
 
  if (!isInitialized || !oauth) return <p>Loading…</p>;
  if (!isAuthenticated) {
    return (
      <button type="button" onClick={() => signIn()}>
        Sign in with Minds
      </button>
    );
  }
 
  async function listMinds() {
    const minds = await oauth.client.listMinds();
    console.log(minds);
  }
 
  return (
    <>
      <button type="button" onClick={() => listMinds()}>
        List minds
      </button>
      <button type="button" onClick={() => signOut()}>
        Log out
      </button>
    </>
  );
}
 
function Callback() {
  const navigate = useNavigate();
  return (
    <LoginCallback
      onSuccess={() => navigate("/", { replace: true })}
      onError={(err) => console.error(err)}
    />
  );
}
Leave LoginCallback without children to show its built-in status. If you pass children, they always render. Send the user home from onSuccess. useMindsConnect() also gives you hasScopes, tokens, and getAccessToken.

Without React

The steps are the same. Share one options module, start sign-in from a button, and finish it on the callback page. The callback is a new page load, so construct MindsOAuth again with the same clientId and redirectUri. onTokensChanged runs in this tab when Connect writes the session: sign-out, a refresh, or a failed refresh that clears the store. It does not run on a fresh page load. Read oauth.tokens when you paint the first view.
// oauth.ts
import { MindsOAuth, MindsScope, type MindsOAuthOptions } from "@animocabrands/minds-connect";
 
export const opts: MindsOAuthOptions = {
  clientId: "YOUR_CLIENT_ID",
  redirectUri: `${window.location.origin}/callback`,
  scopes: [MindsScope.Email, MindsScope.MindsList],
};
 
export const oauth = new MindsOAuth(opts);
// main.ts — home page
import { oauth } from "./oauth";
 
const connectBtn = document.getElementById("connect");
const listBtn = document.getElementById("list");
const logoutBtn = document.getElementById("logout");
 
oauth.onTokensChanged((tokens) => {
  const signedIn = Boolean(tokens);
  if (connectBtn) connectBtn.hidden = signedIn;
  if (listBtn) listBtn.hidden = !signedIn;
  if (logoutBtn) logoutBtn.hidden = !signedIn;
});
 
connectBtn?.addEventListener("click", async () => {
  await oauth.signIn();
});
 
listBtn?.addEventListener("click", async () => {
  const minds = await oauth.client.listMinds();
  console.log(minds);
});
 
logoutBtn?.addEventListener("click", async () => {
  await oauth.signOut();
});
// callback.ts — same path as redirectUri
import { OAuthRedirectError } from "@animocabrands/minds-connect";
import { oauth } from "./oauth";
 
try {
  await oauth.handleRedirect();
  window.location.replace("/");
} catch (err) {
  if (err instanceof OAuthRedirectError) {
    console.error(err.error, err.errorDescription);
  } else {
    console.error(err);
  }
}
handleRedirect() checks the returned state, exchanges the code, and stores the tokens. If the user denies access, or the state does not match, it throws OAuthRedirectError.

Scopes

A scope is what the signed-in user allows your app to do. Put the full set on opts.scopes when you construct the SDK. MindsScope is the set of TypeScript constants for those ids. Both entry points export it. To ask for more later, call signIn({ scopes }) with the full list you want, including scopes they already granted. hasScopes tells you whether that trip is needed.
import { MindsScope } from "@animocabrands/minds-connect";
 
const scopes = [MindsScope.MindsList, MindsScope.ConversationsList, MindsScope.MessagingSend];
 
if (oauth.tokens && !oauth.hasScopes(scopes)) {
  await oauth.signIn({ scopes });
}
In React, use the same list with isAuthenticated, hasScopes, and signIn. oauth.client can only call what that grant includes. listMinds() needs MindsScope.MindsList. Sending a message needs MindsScope.MessagingSend. The method list is on the client library page.
MindsScopeScopeWhat the user allows
MindsScope.EmailemailSee their email
MindsScope.MindsListminds:listSee their Minds
MindsScope.MindsStatusminds:statusSee whether their Minds are on
MindsScope.MindsCognitionminds:cognitionSee their Minds' cognition
MindsScope.MindsSkillsListminds:skills:listSee their Minds' skills
MindsScope.MindsToolsListminds:tools:listSee their Minds' tools
MindsScope.MindsAppsListminds:apps:listSee their Minds' apps
MindsScope.MindsEmailminds:emailSee their Minds' email
MindsScope.MindsWalletsminds:walletsSee their Minds' wallet addresses
MindsScope.MindsAwakenminds:awakenAwaken Minds for them
MindsScope.MindsEnableminds:enableTurn their Minds on
MindsScope.MindsDisableminds:disableTurn their Minds off
MindsScope.MindsSkillsEquipminds:skills:equipEquip skills on their Minds
MindsScope.MindsSkillsUnequipminds:skills:unequipUnequip skills on their Minds
MindsScope.MindsToolsEquipminds:tools:equipEquip tools on their Minds
MindsScope.MindsToolsUnequipminds:tools:unequipUnequip tools on their Minds
MindsScope.MindsAppsEquipminds:apps:equipEquip apps on their Minds
MindsScope.MindsAppsUnequipminds:apps:unequipUnequip apps on their Minds
MindsScope.ConversationsCreateconversations:createStart chats with their Minds
MindsScope.ConversationsListconversations:listSee their chats
MindsScope.ConversationsReadconversations:readSee a chat with their Minds
MindsScope.MessagingSendmessaging:sendSend messages as them
MindsScope.MessagingBeaconmessaging:beaconNudge their Minds
MindsScope.MessagingHistorymessaging:historySee their chat history
MindsScope.MessagingActivityStreammessaging:activity:streamWatch activity for one of their Minds
MindsScope.MessagingStreammessaging:streamWatch messaging events across their Minds

Where the calls run

A web app with no backend can stay in Connect. oauth.client is the client library, already aimed at the signed-in user. Connect depends on @animocabrands/minds-client-lib, so you do not install that package yourself for browser calls.
const minds = await oauth.client.listMinds();
When you also have a backend, keep Connect in the browser for sign-in and refresh. Install the client library on the server, and pass it the access token the page sends you. Listing Minds, sending messages, and the longer work can then run there. The methods are the same. They are documented on the client library page.
import { createMindsClient } from "@animocabrands/minds-client-lib";
 
const accessToken = req.headers.authorization?.replace(/^Bearer\s+/i, "");
const minds = await createMindsClient({ accessToken }).listMinds();
getAccessToken() returns a token that is still valid, refreshing when it is within 60 seconds of expiry. It returns null when there is no session. oauth.tokens is the stored snapshot, and that snapshot can already be expired, so use getAccessToken() when you send the token to your backend.

Sign out

signOut() clears the session in your app and revokes the refresh token. Their Hello Minds account stays signed in, so the next visit can continue without creating an account again.
await oauth.signOut();

Session

The session is stored in localStorage, under minds_oauth_session: plus your client ID. It survives reload and other tabs on this origin. Any script on the page can read it. Pass storage when you want a different store. Extend TokenStore and implement get, set, and clear. signOut() clears that store.
FieldMeaning
accessTokenBearer token for Builder API calls
refreshTokenReplaced on each refresh. The store keeps the latest
expiresInSeconds remaining when you read the store
scopeGranted scopes, separated by spaces. Use hasScopes to check coverage

Options

The same object goes to new MindsOAuth(opts) and to MindsConnect.
OptionRequiredDefaultWhat you pass
clientIdyes—Public client ID from the OAuth Client tab
redirectUriyes—Callback URL you registered, including origin
scopesyes—Full MindsScope list for the first signIn(). Must not be empty. Include MindsScope.Email when you need the account email
storageno"localStorage""localStorage", or your own TokenStore
signIn() can also take state (a string or object you get back from handleRedirect) and scopes (a full list that replaces opts.scopes for that trip).

Methods

AreaWhat you call
Sign-insignIn, handleRedirect
SessiongetAccessToken, tokens, hasScopes, onTokensChanged, signOut
Builder APIclient — the client library (listMinds, sendMessage, and the rest)
ReactMindsConnect, useMindsConnect, LoginCallback
MindsOAuth, MindsScope, OAuthRedirectError, TokenStore, and the types MindsOAuthOptions, TokenSuccess, SignInOptions, and OAuthSession are exported from @animocabrands/minds-connect. The React entry re-exports MindsScope.