@interagentic/react
React hooks and headless components for Interagentic auth + payments. Zero framework dependency — works with any React 18/19 app.
Next.js? Use @interagentic/nextjs instead — it wraps this package with SSR support, middleware, and auto route handlers.
Install
npm install @interagentic/react
Quick Start
Wrap your app with <InteragenticProvider>:
import { InteragenticProvider } from '@interagentic/react';
function App() {
return (
<InteragenticProvider authUrl="https://id.interagentic.dev">
<MyApp />
</InteragenticProvider>
);
}
authUrl is the broker your site uses (INTERAGENTIC_AUTH_URL). Messages
posted by human-in-the-loop popups are only acted on when they come from that
origin — or, for the sign-in popup, from your own — so set it if you run a
broker elsewhere (local development).
Import default styles (optional — all components work headless):
import '@interagentic/react/styles.css';
Hooks
useAuth()
const {
isLoaded, isSignedIn, type, userId, namespaceId, keyId,
email, name, picture, hasLinkedUser, signIn, signOut,
} = useAuth();
isLoaded | boolean | Context finished loading |
isSignedIn | boolean | User is authenticated |
type | 'human' | 'bot' | null | Signed-in human, or an agent |
userId | string | null | Opaque id for this site only — never parse it (see below) |
namespaceId | string | null | Namespace slug (bots) |
keyId | string | null | Bot signing key ID |
email | string | null | User email |
name | string | null | Display name |
picture | string | null | Avatar URL |
hasLinkedUser | boolean | Bot namespace linked to a human |
signIn() | (returnUrl?) => void | Redirect to login |
signOut() | () => Promise<void> | Sign out and redirect to / |
userId is a pairwise id: the broker issues a different one for the same
person at every site, so it is stable and comparable here and meaningless
anywhere else. Store it, compare it, key your own records by it — but do not
parse it or read anything into its shape, which is not part of the contract.
useUser()
const { user, isLoaded } = useUser();
useWallet()
const { isLoaded, balance, currency, refetch } = useWallet();
useLedger()
const { isLoaded, entries, hasMore, loadMore, refetch } = useLedger({ autoLoad: true });
Components
Conditional Rendering
import { SignedIn, SignedOut, HasLinkedUser } from '@interagentic/react';
<SignedIn>Welcome back!</SignedIn>
<SignedOut>Please sign in</SignedOut>
<HasLinkedUser>Human-linked account</HasLinkedUser>
Identity
import { UserButton, NamespaceBadge } from '@interagentic/react';
<UserButton />
<UserButton afterSignOutUrl="/bye" />
<NamespaceBadge />
Payments
import { WalletBalance, TransactionList, TopUpButton } from '@interagentic/react';
<WalletBalance />
<WalletBalance format="full" />
<TransactionList limit={10} />
<TopUpButton amounts={[5_000_000, 10_000_000, 25_000_000]} />
Subscriptions & authorizations
import { SubscriptionList, AuthorizationList, ManageBillingButton } from '@interagentic/react';
<SubscriptionList />
<AuthorizationList />
<ManageBillingButton>Manage billing</ManageBillingButton>
They read through your own backend and link out for anything that
changes billing — cancelling a subscription, revoking an authorization or
opening the billing portal is only accepted from the account holder's own
session on the broker:
<SubscriptionList> | GET {basePath}/payments/subscriptions | {authUrl}/wallet/subscriptions |
<AuthorizationList> | GET {basePath}/payments/provider-authorizations | {authUrl}/wallet/subscriptions |
<ManageBillingButton> | — | {authUrl}/wallet/subscriptions |
With @interagentic/nextjs the two read routes come from the
catch-all handler. On another backend, implement them and forward the
signed-in human's access token to the broker. All three render nothing when
nobody is signed in.
<SpendingControls> was removed in 0.1.0: it called a spending-policy
endpoint that no backend implements.
Styling
All components use data-interagentic-* attributes. Import the default stylesheet or write your own:
[data-interagentic-user-button-trigger] {
}
Override CSS custom properties for theming:
:root {
--interagentic-accent: #8b5cf6;
--interagentic-radius: 12px;
--interagentic-bg: #1a1a1a;
--interagentic-fg: #fafafa;
}
asChild Pattern
Components that support asChild replace their wrapper with your element (Radix-style):
<NamespaceBadge asChild>
<code className="my-badge" />
</NamespaceBadge>
Provider Props
initialState | InitialAuthState | — | SSR-hydrated auth state |
basePath | string | "/api/interagentic" | Base path for auth API routes |
authUrl | string | "https://id.interagentic.dev" | Broker origin trusted for popup flow messages |
Types
All types are exported:
import type {
AuthState, AuthType, InteragenticUser, InitialAuthState,
WalletState, LedgerEntry,
SubscriptionData, AuthorizationData,
} from '@interagentic/react';
Popups and postMessage
<ActionButton> opens a popup for any 4XX response carrying a payTo /
resolve_url (payment approval, sign-in, connecting an app) and retries the
request when the flow reports success. It only ever opens http(s) URLs, and
only acts on a message that comes from the popup it opened and from a
trusted origin: interagentic:flow:completed / interagentic:flow:cancelled
from authUrl, interagentic:auth-complete from your own origin. The
provider applies the same rule before refreshing the session.