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

Official libraries

JavaScript / TypeScript SDK

Identify users and accounts, control the widget, and call the Services catalog from your app.

View package

JavaScript / TypeScript SDK logo

React SDK

AimdocProvider for the in-app agent, plus the Services catalog embed, in-page catalog, and headless hooks.

View package

React SDK logo

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:

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.

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.objectives updates account-level objectives
  • top-level objectives updates person-level objectives for that user
  • Statuses are in_progress or done
  • 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
  • askAboutObjective opens the agent on that step (optional message and agentId overrides)

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:

  • autoOpenfalse suppresses a configured auto-open; true enables 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 explicit 0 is 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.
  • hiddentrue fully suppresses the widget for this page view. false does not override visibility rules from the dashboard; it only declines to hide.
  • cornerOffset — optional non-negative pixel offsets. x moves desktop corner-anchored surfaces inward from the configured left/right edge and y moves 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-width on <html> (400px when open, 0px when closed):
.my-fixed-header {
  padding-right: var(--aimdoc-sidebar-width, 0px);
  transition: padding-right 300ms ease-out;
}
  • aimdoc:sidebar window event with { open, width, position } in detail, for JavaScript-driven layouts
  • A synthetic window resize event 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 to https://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, call useAimdoc().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

Was this page helpful?