Aimdoc AI SDKs
Aimdoc provides official SDKs for JavaScript/TypeScript and React so you can control chat behavior directly from your app.
Use these SDKs to:
- Identify signed-in users when your embedded agent is running in App Mode (Activate)
- Associate users with application accounts or tenants
- Override widget display behavior (auto-open, hide, presentation mode) per page or user segment
- Open the chat programmatically
- Send messages into the conversation flow
- Attach host application context to the next user message
- Embed the Services catalog so signed-in customers can run and automate the capabilities you published
identify is for App Mode (Activate) — signed-in users inside your product.
It does not replace Visitor Identification for
anonymous website traffic. If you have not added the widget yet, start with
Deploy.
Official libraries
JavaScript / TypeScript SDK
Identify users and accounts, control the widget, and call the Services catalog from your app.
React SDK
AimdocProvider for the in-app agent, plus the Services catalog embed, in-page catalog, and headless hooks.
Install
Install the package that fits your stack:
# JavaScript / TypeScript
npm install @aimdoc/sdk
# React
npm install @aimdoc/sdk-react
Upgrade to the latest package for TypeScript support for structured account
identity. The message protocol is additive, so older JavaScript runtimes can
forward the new field, but older TypeScript definitions do not accept the
account object.
JavaScript SDK
The JavaScript SDK exports a singleton aimdoc client:
aimdoc.identify(...) is currently supported for embedded agents running in App Mode, also called Operator Mode. For standard website agents, the SDK call is not processed unless App Mode is active for the current page.
import { aimdoc } from '@aimdoc/sdk'
aimdoc.identify({
external_id: 'user_123',
email: 'jane@company.com',
first_name: 'Jane',
last_name: 'Doe',
company: 'Acme Inc', // Legacy contact-level company field
account: {
external_id: 'account_456',
name: 'Acme Inc',
domain: 'acme.com',
},
attributes: {
plan: 'pro',
},
user_hash: accountAwareHashFromYourServer,
})
aimdoc.openChat({ agentId: 'your-agent-id' })
aimdoc.sendMessage('Can you show enterprise pricing?', { agentId: 'your-agent-id' })
aimdoc.setNextMessageContext(
`The user is viewing the onboarding checklist.
They have already connected Salesforce and imported teammates.
They are currently on the "Invite teammates" step, with the invite modal open.
The last invite attempt failed because the email domain did not match their company domain.`,
{ agentId: 'your-agent-id' },
)
aimdoc.setDisplayOptions({ autoOpen: false })
Available methods
identify(user)— attach durable user, account, and profile identity for App Mode sessions.openChat(options?)— open the chat launcher/widget.sendMessage(message, options?)— send a message as the current visitor/user.setNextMessageContext(context, options?)— add host application context to the next user message only.askAboutObjective(objective, options?)— open the agent about one customer-facing objective.setDisplayOptions(options, target?)— override auto-open, visibility, and presentation mode for this page view.
agentId is optional in openChat, sendMessage, setNextMessageContext,
askAboutObjective, and setDisplayOptions. Provide it when your page can
target multiple agents.
Use identify for durable user and account identity. Use
setNextMessageContext for transient app state that should help the agent
answer the next message, such as where the user is in onboarding, what modal
is open, which validation error just appeared, or what workflow step they are
trying to complete. The context is added to the agent's prompt for one turn
only, is not stored as a visible chat message, and is cleared after the next
message payload is sent. You can call it while the chat is closed; the widget
holds the context and attaches it to the next message.
The first argument can be a string or any JSON-serializable value. Objects and arrays are serialized as formatted JSON before they are added to the agent prompt.
Identify users and accounts
The user external_id and email are required. To associate the user with
one of your application accounts or tenants, include:
account: {
external_id: 'account_456', // Required: your stable account or tenant ID
name: 'Acme Inc', // Optional
domain: 'acme.com', // Optional
}
account.name is the account name. The legacy top-level company field
continues to update the contact's company field, but it does not name the
account.
If account.domain is omitted, Aimdoc uses the contact's work-email domain
when available. An external ID is enough to identify an account for users with
personal email addresses. A non-empty supplied name updates the account;
omitted or empty values do not clear existing data.
Aimdoc checks an explicitly supplied domain and external ID together. If they already belong to different account records, the account update is rejected rather than silently overwriting or merging records. A domain inferred from the user's work email is only a hint: if it conflicts with the external ID, Aimdoc keeps the external-ID association and discards the inferred domain.
Every identify call containing an account object or objective
claims must include a valid user_hash, even if organization-wide
identity-verification enforcement is disabled.
Objective claims
When you define objectives on an agent, your app can sync
progress on identify as users complete milestones themselves.
aimdoc.identify({
external_id: 'user_123',
email: 'jane@company.com',
account: {
external_id: 'account_456',
name: 'Acme Inc',
domain: 'acme.com',
objectives: {
trial_started: 'done',
},
},
objectives: {
completed_onboarding: 'done',
},
user_hash: v4HashFromYourServer,
})
account.objectivesupdates account-level objectives- top-level
objectivesupdates person-level objectives for that user - Statuses are
in_progressordone - Keys must match objectives published on the agent this identify targets
Any identify that includes objective claims requires a signed v4 payload. See Identity Verification.
Customer-facing checklist
When you mark an objective Show to your users, your product can render it as a checklist item for the identified customer — for example an onboarding list or getting-started card.
The list uses the same identified Services session as the catalog. Configure the client with a publishable key and identity assertion first, then read the checklist:
import { aimdoc } from '@aimdoc/sdk'
// After aimdoc.configureServices({ publishableKey, getIdentityAssertion })
const objectives = await aimdoc.services.listObjectives()
// [{ key, name, level, status: 'not_started' | 'in_progress' | 'done',
// completed_at, agent_id }]
aimdoc.askAboutObjective(objectives[0])
- Only names and statuses are returned — the objective description (the agent's completion rubric) stays private
- Skipped objectives are omitted; anonymous sessions get an empty list
- Pass
{ agentId }to narrow to one agent; by default every published agent's customer-facing objectives are included askAboutObjectiveopens the agent on that step (optionalmessageandagentIdoverrides)
There is no drop-in checklist component. Render the list in your own UI and
refetch when the chat closes or the window regains focus.
useObjectives in @aimdoc/sdk-react does that for you:
import { aimdoc } from '@aimdoc/sdk'
import { useObjectives } from '@aimdoc/sdk-react'
function GettingStarted() {
const { incomplete, completed, loading } = useObjectives(aimdoc.services)
if (loading || incomplete.length === 0) return null
return (
<ul>
{incomplete.map((objective) => (
<li key={objective.key}>
{objective.name}
<button onClick={() => aimdoc.askAboutObjective(objective)}>
Ask the agent
</button>
</li>
))}
<li>{completed.length} done</li>
</ul>
)
}
Requires @aimdoc/sdk 2.10 or later and @aimdoc/sdk-react 2.6 or later.
Identity verification
Secure identify calls with a user_hash generated by your server. Never
expose the signing secret in browser code.
Identifies that include an account object or objective claims always require
a signed canonical payload (v2–v4). User-only calls require a signature after
you enable organization-wide enforcement, and invalid signatures are always
rejected.
See Identity Verification for signing-secret setup, canonical payloads, implementation examples, enforcement, and rotation.
Display options
Use setDisplayOptions to override the widget's display behavior for the
current page view — for example, suppressing auto-open for existing users
while keeping it for new ones, or opening a specific route in the sidebar:
import { aimdoc } from '@aimdoc/sdk'
aimdoc.setDisplayOptions({ autoOpen: isExistingUser ? false : true })
aimdoc.setDisplayOptions({ expandedMode: 'sidebar' })
Options override the agent-level settings from the Aimdoc dashboard and stay in
effect until the page unloads. Call as early as you like — auto-open overrides
apply as long as the auto-open timer has not fired yet. Repeated calls merge
key by key, with the latest value winning. setDisplayOptions never closes a
chat that is already open, so autoOpen: false after the widget has opened is
a no-op. Presentation mode changes (collapsedMode / expandedMode) apply
immediately, including re-laying out a chat that is already open.
To move a corner-anchored widget away from overlapping page controls, provide an additional horizontal and/or vertical offset in pixels:
aimdoc.setDisplayOptions({ cornerOffset: { x: 16, y: 80 } })
Available options:
autoOpen—falsesuppresses a configured auto-open;trueenables auto-open even when the agent has it disabled (using the agent's configured delay, or 3 seconds when it has none).autoOpenSeconds— seconds before auto-open fires, overriding the agent's configured delay. An explicit0is honored.openOncePerSession— overrides the agent's "only auto-open once per session" setting. The once-per-session flag is recorded only when the widget actually opens, so a page that suppresses auto-open does not consume it.hidden—truefully suppresses the widget for this page view.falsedoes not override visibility rules from the dashboard; it only declines to hide.cornerOffset— optional non-negative pixel offsets.xmoves desktop corner-anchored surfaces inward from the configured left/right edge andymoves them upward from the bottom. Partial updates merge by axis. Mobile fullscreen layouts, centered copilot surfaces, expanded sidebars, and an operator position that the user has dragged are unchanged.collapsedMode—'chat'(corner bubble) or'copilot'(centered input bar), overriding the agent's configured collapsed presentation. Ignored on mobile and in App Mode, which always uses its command-bar pill.expandedMode— the layout the widget opens into:'chat'(corner chat window),'sidebar'(full-height panel that pushes page content aside), or'copilot'(centered modal). Mobile always uses fullscreen chat. App Mode supports'chat'and'sidebar'only and defaults to'sidebar'.
Auto-open options apply to the website widget. They do not affect the App Mode
operator surface. hidden and expandedMode work on both surfaces (with the
App Mode clamps above).
Sidebar layout and fixed elements
The sidebar pushes page content aside by padding the document element, which
reflows normal-flow and position: sticky layouts. Elements sized against the
viewport — position: fixed headers or buttons, 100vw-wide sections — do not
respond to that padding and can sit underneath the sidebar. Use these hooks to
adapt them:
- CSS variable
--aimdoc-sidebar-widthon<html>(400pxwhen open,0pxwhen closed):
.my-fixed-header {
padding-right: var(--aimdoc-sidebar-width, 0px);
transition: padding-right 300ms ease-out;
}
aimdoc:sidebarwindow event with{ open, width, position }indetail, for JavaScript-driven layouts- A synthetic window
resizeevent after the slide animation settles, so container-measuring components (charts, editors) re-measure
If a page hosts multiple agents, pass agentId to target one widget:
aimdoc.setDisplayOptions({ autoOpen: false }, { agentId: 'your-agent-id' })
If you embed with the static script snippet instead of the npm SDK, pass the same options at initialization:
window.aimdoc.initAgentChat('ai-assistant', {
agentId: 'YOUR_AGENT_ID',
displayOptions: {
autoOpen: false,
expandedMode: 'sidebar',
cornerOffset: { x: 16, y: 80 },
},
})
Dashboard auto-open defaults are configured under Configuration. Website presentation defaults (chatbot, copilot, sidebar) are under Appearance.
React SDK
The React SDK gives you AimdocProvider and useAimdoc.
'use client'
import { AimdocProvider } from '@aimdoc/sdk-react'
export default function AppRoot() {
return (
<AimdocProvider
agentId="your-agent-id"
displayOptions={{ autoOpen: false }}
>
<App />
</AimdocProvider>
)
}
Inside child components, call the SDK through the hook:
'use client'
import { useAimdoc } from '@aimdoc/sdk-react'
export function OpenChatButton() {
const { identify, openChat } = useAimdoc()
const onClick = () => {
identify({
external_id: 'user_123',
email: 'jane@company.com',
account: {
external_id: 'account_456',
name: 'Acme Inc',
domain: 'acme.com',
},
user_hash: accountAwareHashFromYourServer,
})
openChat()
}
return <button onClick={onClick}>Chat with sales</button>
}
AimdocProvider props
agentId(required) — your Aimdoc agent ID.scriptUrl(optional) — custom script URL. Defaults tohttps://app.aimdoc.ai/embedded.bundle.js.displayOptions(optional) — display overrides applied at init and whenever the value changes. Changing this prop does not tear down the widget. For imperative per-page control, calluseAimdoc().setDisplayOptions(...)(same options as Display options).
Services catalog
The same catalog your agent offers can run inside your product. The browser never sees an API key. Your frontend uses a publishable key (Services → SDK) plus a short-lived identity assertion your server signs with the identity-verification secret.
Create a test or live publishable key in Services → SDK. The key is safe
to ship in frontend code: it selects an organization and environment but
does not grant service access. Use @aimdoc/sdk 2.10 or later and
@aimdoc/sdk-react 2.6 or later for everything on this page (including the
customer-facing checklist).
Sign the identity assertion
// Server only — never expose AIMDOC_IDENTITY_SECRET in browser code.
import { createServicesIdentityAssertion } from '@aimdoc/sdk/server'
export async function getAimdocServicesIdentity(user: User) {
return createServicesIdentityAssertion({
publishableKey: process.env.NEXT_PUBLIC_AIMDOC_SERVICES_KEY!,
identitySecret: process.env.AIMDOC_IDENTITY_SECRET!,
contact: {
external_id: user.id,
email: user.email,
first_name: user.firstName,
account: user.account
? { external_id: user.account.id, name: user.account.name }
: undefined,
},
})
}
Assertions expire in minutes and carry a one-time jti. Always serve a
fresh one per request.
Configure the browser client
import { aimdoc } from '@aimdoc/sdk'
aimdoc.configureServices({
publishableKey: import.meta.env.VITE_AIMDOC_SERVICES_KEY,
getIdentityAssertion: async () => {
const response = await fetch('/api/aimdoc/services-identity', {
credentials: 'same-origin',
})
if (!response.ok) throw new Error('Could not identify Services user')
return response.text()
},
})
const services = await aimdoc.services.list()
const details = await aimdoc.services.get(services[0].key)
const run = await aimdoc.services.invoke(details.key, { topic: 'Q3 plan' })
const history = await aimdoc.services.listRuns({ service: details.key, limit: 10 })
const latest = await aimdoc.services.getRun(run.id)
await aimdoc.services.cancelRun(run.id)
Catalog entries carry an allowance when the customer's access is counted:
limit, used, remaining, exhausted, and period_end (null for a
one-time pack). getAllowance(key) returns the same object on its own.
Omit getIdentityAssertion for an anonymous, discovery-only catalog.
Anonymous visitors can see services whose Discover default allows it, but
cannot invoke or request access.
Managed embed
AimdocServicesEmbed from @aimdoc/sdk-react mounts an Aimdoc-hosted
catalog page in an iframe. Catalog UI is deployed by Aimdoc, so fixes reach
you without a package upgrade. Identity stays with your application: your
backend signs each assertion and the component relays it.
import { AimdocServicesEmbed } from '@aimdoc/sdk-react'
export function CustomerServices() {
return (
<AimdocServicesEmbed
publishableKey={import.meta.env.VITE_AIMDOC_SERVICES_KEY}
getIdentityAssertion={async () => {
const response = await fetch('/api/aimdoc/services-identity')
if (!response.ok) throw new Error('Unable to identify Services user')
return response.text()
}}
theme={{ '--aimdoc-services-brand': '#0f766e' }}
/>
)
}
Pass initialServiceKey to open on one service. onRunStarted,
onAccessRequested, onAutomationCreated, onCheckoutStarted, and
onPurchaseCompleted callbacks let your app react to what the customer does
inside the embed.
In-page catalog
AimdocServiceCatalog renders in your DOM and versions with the package.
It includes catalog cards, a schema-driven input form, direct invocation,
request-access actions, live run progress, Stripe checkout for priced
services, and — when allowed — scheduled automations.
import { aimdoc } from '@aimdoc/sdk'
import { AimdocServiceCatalog } from '@aimdoc/sdk-react'
export function CustomerServices() {
return <AimdocServiceCatalog client={aimdoc.services} />
}
Pass renderMarkdown if you want rich markdown for results. Brand with
--aimdoc-services-* CSS variables on a parent element. Set
checkoutReturnUrl if buyers should come back to a page other than the one
they started checkout from.
One service, in place
When a service belongs on a specific page of your product rather than in a catalog, render it there:
import { aimdoc } from '@aimdoc/sdk'
import { AimdocServiceLauncher } from '@aimdoc/sdk-react'
export function WeeklyAuditCard() {
return (
<AimdocServiceLauncher
client={aimdoc.services}
serviceKey="weekly-account-audit"
inputs={{ scope: 'all-workspaces' }}
onCompleted={(run) => console.log(run.status)}
/>
)
}
AimdocServiceLauncher renders the service's inputs, the customer's
remaining runs when access is counted, a start button, and live progress for
the run. AimdocRunProgress renders the same progress view for a run you
started yourself: a plan checklist, the current step, elapsed time, the
result, and a cancel button while the run is in flight.
Run progress
Runs stream their progress as they execute. streamRun opens a live stream
and falls back to polling when streaming is unavailable:
const stream = aimdoc.services.streamRun(run.id, {
onEvent: (event) => {
// event.kind is 'plan', 'step', 'status', or 'snapshot' (polling only)
console.log(event.seq, event.kind, event.payload)
},
})
// later
stream.close()
Pass after with the last seq you processed to resume without replaying
earlier events. Runs end in succeeded, failed, cancelled, or
skipped; @aimdoc/sdk-react exports that set as TERMINAL_RUN_STATUSES.
Purchases
For a service sold through Stripe checkout,
startCheckout returns a hosted Checkout URL. Open it in the top-level
window; Stripe Checkout refuses to load inside an iframe. Then poll the order
until it is active. Access is granted only after Stripe confirms the
payment, never on the redirect alone.
const checkout = await aimdoc.services.startCheckout(details.key, {
returnUrl: window.location.href,
})
window.open(checkout.url, '_blank')
const order = await aimdoc.services.getOrder(checkout.order_id)
// poll until order.status === 'active', then refresh the catalog entry
const purchases = await aimdoc.services.listOrders()
const portal = await aimdoc.services.createPortalSession(purchases[0].id)
window.open(portal.url, '_blank')
listOrders returns the customer's purchases newest first, each with
status, current_period_end, cancel_at_period_end, and service_name.
createPortalSession opens Stripe's billing portal on your connected
account so the customer can update their card or cancel at period end.
AimdocServiceCatalog and AimdocServicesEmbed handle all of this for you
and expose onCheckoutStarted and onPurchaseCompleted.
Headless hooks
useServiceCatalog, useServiceLauncher, useRunProgress, and
useServiceAutomations are the same engine without the markup. Use them for
a fully custom UI. (useServiceRun still works but polls only; prefer
useRunProgress.) A verified customer can schedule any service they are
allowed to invoke and automate; access is re-checked on every call and
again at every scheduled execution. createAutomation accepts the same flat
inputs you pass to invoke.
From your backend, emit events or trigger a service with an API key instead of the publishable key.
Next steps
- Services for authoring, access, and automations
- Events to emit product events and trigger services from your server
- Deploy for script/widget setup details
- Identity Verification for signing and enforcement
- Objectives for journey goals, customer-facing checklists, and identify sync
- App Mode for in-product agent guidance
- API & Webhooks Overview for backend integrations
- Webhooks for event delivery patterns
- API Reference (Redoc)