Official client for the Gradient Labs API. Written
in TypeScript, ships full type declarations and both ESM and CommonJS builds, and
has zero runtime dependencies (it uses Node's built-in fetch and crypto).
Requires Node.js 20 or newer.
npm install @gradientlabs/clientimport { GradientLabs } from "@gradientlabs/client";
const client = new GradientLabs({ apiKey: process.env.GRADIENT_LABS_API_KEY! });
const conversation = await client.conversations.start({
id: "ticket-12345",
customer_id: "customer-678",
channel: "web",
assignee_type: "AI Agent",
});
console.log(conversation.status);The client is organised into resource namespaces, e.g. client.conversations,
client.tools, client.procedures. There are two API key roles:
- Integration — conversation runtime endpoints (
conversations,outboundConversations,backOfficeTasks,voice). - Management — configuration endpoints (
tools,articles,topics,procedures,handOffTargets,resourceSources,resourceTypes,secrets,notes,terminologySubstitutions,trafficGroups,ipAddresses).
const client = new GradientLabs({
apiKey: "sk_live_...", // required
baseUrl: "https://api.gradient-labs.ai", // optional (this is the default)
webhookSigningKey: "whsec_...", // optional, required to verify webhooks
webhookLeewayMs: 5 * 60 * 1000, // optional, default 5 minutes
timeoutMs: 30_000, // optional per-request timeout
fetch: myFetch, // optional, inject a custom fetch (tests, proxies, instrumentation)
});Every method accepts an optional final argument carrying an AbortSignal for
cancellation:
const controller = new AbortController();
const tools = await client.tools.list({ signal: controller.signal });Non-2xx responses throw an ApiError; client misconfiguration throws a
ConfigurationError. Both extend GradientLabsError.
import { ApiError, ErrorCode } from "@gradientlabs/client";
try {
await client.conversations.get("missing");
} catch (err) {
if (err instanceof ApiError) {
console.error(err.statusCode, err.code, err.message);
if (err.code === ErrorCode.NotFound) {
// handle 404
}
console.error("trace id:", err.traceId); // give this to support
}
}The client never retries failed requests — retry policy is left to you.
List endpoints that paginate return a Page<T> with opaque next/prev
cursors. Use listAll() to iterate every page automatically:
for await (const procedure of client.procedures.listAll()) {
console.log(procedure.name);
}
// or page manually:
const page = await client.procedures.list();
const next = await client.procedures.list({ cursor: page.pageInfo.next });client.outboundConversations starts conversations the AI agent initiates, one
method per channel. All three return { conversation_id }.
// Live chat — support_platform is required.
await client.outboundConversations.startChat({
customer_id: "customer-678",
procedure_id: "proc_abc",
support_platform: "intercom",
body: "Hi! Your order has shipped.", // optional; the agent writes one if omitted
customer_support_platform_identifiers: [
{ support_platform: "intercom", type: "intercom_user", value: "6953e162a988d9ef0f73ef9b" },
],
});
// Email — subject and body must be supplied together, or both omitted.
await client.outboundConversations.startEmail({
customer_id: "customer-678",
procedure_id: "proc_abc",
support_platform: "zendesk",
subject: "Your recent order",
body: "Your order has shipped.",
customer_support_platform_identifiers: [
{ support_platform: "zendesk", type: "zendesk_support_user", value: "42" },
],
});
// Phone — no support_platform; the call is placed over voice.
await client.outboundConversations.startPhone({
customer_id: "customer-678",
procedure_id: "proc_abc",
to_phone_number: "+14155551234",
from_phone_number: "+14155559876", // must be provisioned for your company
});customer_id is always your own identifier for the customer. Third-party
platform IDs go in customer_support_platform_identifiers, keyed by platform —
Zendesk requires type zendesk_support_user and Salesforce
salesforce_contact_id. The platform the message is delivered on needs an
identifier there, unless the customer already carries one from an earlier
conversation.
Construct the client with your webhookSigningKey, then verify and parse
incoming requests. Pass the raw request body — the signature is computed over
the exact bytes received.
import { GradientLabs, InvalidWebhookSignatureError } from "@gradientlabs/client";
const client = new GradientLabs({
apiKey: process.env.GRADIENT_LABS_API_KEY!,
webhookSigningKey: process.env.GL_WEBHOOK_SIGNING_KEY!,
});
// Express example (use express.raw() so req.body is the raw payload):
app.post("/webhooks", express.raw({ type: "*/*" }), (req, res) => {
try {
const { event, token } = client.webhooks.parse({
body: req.body, // Buffer
headers: req.headers,
});
switch (event.type) {
case "agent.message":
console.log(event.data.body);
break;
case "conversation.hand_off":
console.log(event.data.reason_code);
break;
// ...other event types
}
res.sendStatus(200);
} catch (err) {
if (err instanceof InvalidWebhookSignatureError) {
res.sendStatus(401);
} else {
res.sendStatus(500);
}
}
});event is a discriminated union on event.type, so narrowing gives you a
fully-typed event.data. The optional X-GradientLabs-Token header is returned
as token.
Supported event types: agent.message, conversation.hand_off,
conversation.finished, action.execute, resource.pull,
back-office-task.complete, back-office-task.hand-off, back-office-task.fail.
See the examples/ directory for runnable examples covering
conversations, tools, articles, procedures, resources, back-office tasks, and a
webhook server.
Releases are published to npm automatically when a version tag is pushed to main.
One-time setup (if not already done):
- On npmjs.com, go to the
@gradientlabs/clientpackage → Settings → Trusted Publishers → Add trusted publisher. - Select GitHub Actions and fill in owner
gradientlabs-ai, repositorygradientlabs-nodejs, workflowpublish.yml.
To publish a new version:
- Merge all changes into
main. - Update the version in
package.json, merge that tomain, and pull locally. - Push a version tag matching the
package.jsonversion:git tag v1.2.3 git push origin v1.2.3
- The publish workflow runs automatically: it verifies the tag matches the
package.jsonversion, builds the library, and publishes it to npm.
npm install
npm run build # dual ESM/CJS + type declarations
npm test # vitest (no network required)
npm run lint
npm run typecheck
npm run format