Integration3 min read

Governed HR agents in your customers' existing Gusto companies

Connect each customer's Gusto company to a governed agent profile on Naïve — read-only payroll and onboarding readiness today, with kit-level tool policy and audit.

Read the docs →

/naive /naive /naive /naive /naive /naive /naive /naive /naive /naive
/naive /naive /naive /naive /naive /naive /naive /naive /naive /naive
/naive /naive /naive /naive /naive /naive /naive /naive /naive /naive
/naive /naive /naive /naive /naive /naive /naive /naive /naive /naive
/naive /naive /naive /naive /naive /naive /naive /naive /naive /naive
/naive /naive /naive /naive /naive /naive /naive /naive /naive /naive
/naive /naive /naive /naive /naive /naive /naive /naive /naive /naive
/naive /naive /naive /naive /naive /naive /naive /naive /naive /naive
/naive /naive /naive /naive /naive /naive /naive /naive /naive /naive
/naive /naive /naive /naive /naive /naive /naive /naive /naive /naive
/naive /naive /naive /naive /naive /naive /naive /naive /naive /naive
/naive /naive /naive /naive /naive /naive /naive /naive /naive /naive
/naive /naive /naive /naive /naive /naive /naive /naive /naive /naive
/naive /naive /naive /naive /naive /naive /naive /naive /naive /naive
Integration
TL;DR
  • 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:

  1. A Naïve workspace key from Studio
  2. An Account Kit with gusto allowlisted and an explicit tools.gusto.enable read list
  3. 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 runAgent-submit payroll (no verified submit tool in catalog)
List employees + per-employee / company onboarding statusconnections.execute returning pending_approval for Gusto writes
Summarize findings with client.llmReplacing Gusto UI for I-9 or tax filing
Draft manager email; human sendsAutonomous completion of regulated HR steps

Connect → discover → execute

  1. Connectnaive.forUser(id).connections.connect("gusto"). Agent-initiated connects default to approval-gated (connections.connect in default approval actions). If pending_approval, a human approves first; then retry connect for redirectUrl.
  2. Discoverconnections.tools("gusto") lists catalog tools for the toolkit (paginated — not your kit allowlist). Use it to pick slugs, then pin them on the kit with tools.gusto.enable.
  3. Executeconnections.execute("gusto", "<TOOL_SLUG>", args) returns { successful, error, data }. Tools outside the kit enable list throw forbidden.
  4. Summarizeclient.llm.chat(...) on the same scoped client — not the root naive.llm. Minimize what you pass into the model.

Quick start

npm install @usenaive-sdk/server
import { 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

  1. Workspace key in Studio
  2. Account Kit with gusto allowlist + explicit tools.gusto.enable (reads only)
  3. Tenant user per customer + kit assignment
  4. Connect customer's Gusto (approval → hosted link → ACTIVE)
  5. Run GUSTO_GET_TOKEN_INFO for company_uuid, then payroll blocker / onboarding tools
  6. 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

Frequently Asked Questions
What does Naïve support for Gusto today?+
Per-customer connections into existing Gusto companies, governed connect/tools/execute via `/connections`, tenant isolation with naive.forUser(), and Account Kit policy on every call. Naïve does not replace Gusto or submit payroll on the agent's behalf — humans review and act in Gusto.
How do I restrict dangerous Gusto tools?+
Do not rely on toolkit allowlist alone — Gusto exposes many write tools. Use tools.gusto.enable with an explicit read-only list. connections.execute is not approval-gated by default; gate writes in kit policy and in your product UX.
What is the toolkit slug?+
`gusto`. Confirm with `naive connections list --search gusto` or `GET /v1/toolkits?search=gusto`.
Does connections.tools show only kit-allowed tools?+
No. It returns the toolkit catalog (paginated). Account Kit enable/disable is enforced on connections.execute — tools outside your enable list return forbidden.
Do customers sign into Naïve?+
No. You create tenant users via API. Customers only complete the Gusto connection at the hosted link your app serves.
NT
Naïve Team

Building the agent-native backend.

Keep reading