- ›
Gusto stays the system of record— Naïve gives each customer a governed agent profile with their own Gusto connection - ›Ship read-only value first: payroll blockers, onboarding status, manager summaries via Gusto read tools
- ›
Account Kits allowlist the `gusto` toolkit and enable specific tools— a full toolkit allowlist is not read-only - ›Connecting Gusto may return pending_approval (HTTP 202); execute is filtered by kit tool policy, not execute-level approvals today
Multi-tenant HR products break when every customer shares one Gusto admin token. Naïve gives each customer a governed agent profile — one tenant user, one Gusto connection, Account Kit policy enforced at execution time, audit and revoke on the same identity.
Gusto stays the system of record. Naïve is the controlled-autonomy layer: connect, discover tools, execute reads server-side (tokens never enter the model), and compose /llm, /email, or /cron on that profile — without building per-customer token storage and policy yourself.
Prerequisites
Before you wire this in production:
- A Naïve workspace key from Studio
- An Account Kit with
gustoallowlisted and an explicittools.gusto.enableread list - Verified tool slugs after connect —
connections.tools("gusto")lists the catalog (default page size ~100, API max 200). It is not kit-filtered; kit policy is enforced at execute
In scope today vs not yet
| In scope (read-only readiness) | Not in this integration path yet |
|---|---|
| List payroll blockers before a run | Agent-submit payroll (no verified submit tool in catalog) |
| List employees + per-employee / company onboarding status | connections.execute returning pending_approval for Gusto writes |
Summarize findings with client.llm | Replacing Gusto UI for I-9 or tax filing |
| Draft manager email; human sends | Autonomous completion of regulated HR steps |
Connect → discover → execute
- Connect —
naive.forUser(id).connections.connect("gusto"). Agent-initiated connects default to approval-gated (connections.connectin default approval actions). Ifpending_approval, a human approves first; then retry connect forredirectUrl. - Discover —
connections.tools("gusto")lists catalog tools for the toolkit (paginated — not your kit allowlist). Use it to pick slugs, then pin them on the kit withtools.gusto.enable. - Execute —
connections.execute("gusto", "<TOOL_SLUG>", args)returns{ successful, error, data }. Tools outside the kit enable list throwforbidden. - Summarize —
client.llm.chat(...)on the same scoped client — not the rootnaive.llm. Minimize what you pass into the model.
Quick start
npm install @usenaive-sdk/serverimport { Naive, isPendingApproval } from "@usenaive-sdk/server";
const naive = new Naive({ apiKey: process.env.NAIVE_API_KEY! });
const client = naive.forUser("customer-id");
const res = await client.connections.connect("gusto", {
callbackUrl: "https://yourapp.com/connections/gusto/done",
});
if (isPendingApproval(res)) {
const approval = await client.approvals.wait(res.approval_id);
if (approval.status !== "approved") throw new Error("Gusto connect was not approved");
const retry = await client.connections.connect("gusto", {
callbackUrl: "https://yourapp.com/connections/gusto/done",
});
if (!isPendingApproval(retry) && retry.redirectUrl) {
// Send the customer to retry.redirectUrl to connect their Gusto company
}
} else if (res.redirectUrl) {
// Send the customer to res.redirectUrl to connect their Gusto company
}
// Confirm ACTIVE before executing — see client.connections.connected()
const token = await client.connections.execute("gusto", "GUSTO_GET_TOKEN_INFO", {});
if (!token.successful) throw new Error(token.error ?? "GUSTO_GET_TOKEN_INFO failed");
const companyUuid = (token.data as { company_uuid?: string })?.company_uuid;
if (!companyUuid) throw new Error("Could not resolve Gusto company id");
const result = await client.connections.execute("gusto", "GUSTO_LIST_PAYROLL_BLOCKERS", {
company_uuid: companyUuid,
});
if (!result.successful) throw new Error(result.error ?? "Gusto execute failed");
// Minimize what enters the model — pass counts/labels, not full employee records
const payload = result.data as { blockers?: { type?: string; message?: string }[] };
const safe = {
blocker_count: payload.blockers?.length ?? 0,
summary_fields: payload.blockers?.slice(0, 20)?.map((b) => ({
type: b.type,
message: b.message,
})),
};
const { choices } = await client.llm.chat({
model: "anthropic/claude-sonnet-4.6",
messages: [{
role: "user",
content: `Summarize payroll blockers for a manager:\n\n${JSON.stringify(safe)}`,
}],
});Tool slugs and response shapes come from the connections catalog — confirm with connections.tools("gusto") in your workspace before production, then mirror only the reads you need onto the kit enable list.
Define read-only Gusto tools on the Account Kit before launch — see the full build guide.
First build checklist
- Workspace key in Studio
- Account Kit with
gustoallowlist + explicittools.gusto.enable(reads only) - Tenant user per customer + kit assignment
- Connect customer's Gusto (approval → hosted link →
ACTIVE) - Run
GUSTO_GET_TOKEN_INFOforcompany_uuid, then payroll blocker / onboarding tools - Summarize with
client.llm; humans act in Gusto for submits and regulated steps
Common errors: forbidden (tool not on kit enable list), pending_approval on connect (expected for agent calls), not connected (rerun connect flow), wrong arg names (company_uuid vs company_id — check the tool schema).
Migrating from DIY integrations or direct Composio: keep Gusto as system of record; swap token storage and policy for Naïve forUser + Account Kits.
Where to go next
- Build a Gusto payroll & onboarding readiness agent
- Introducing connections
- Connections docs
- Building AI agents into your SaaS
What does Naïve support for Gusto today?+
How do I restrict dangerous Gusto tools?+
What is the toolkit slug?+
Does connections.tools show only kit-allowed tools?+
Do customers sign into Naïve?+
Building the agent-native backend.
The connections primitive gives each end-user's agents authenticated access to 1,000+ third-party apps — per user, gated by the Account Kit, in one surface.
Multi-tenant Gusto readiness on Naïve: read-only payroll blockers and onboarding status, Account Kit tool allowlists, full connect lifecycle, client.llm summaries.
An Account Kit is a reusable policy template that governs what a tenant user's AI agents can do — which primitives are enabled and which apps they can connect.
Building multi-tenant AI agents into your SaaS means giving each customer an isolated, governed agent. Here's the full architecture and how to ship it.