Quickstart for developers
Every request sends an API key as a bearer token. Keys carry scopes, so a key made for calls cannot manage numbers. Get a key in the portal, then place your first call in a few lines.
Install the SDK
The TypeScript SDK wraps the REST API, the live event stream and webhook verification. Every call also works with plain HTTP.
npm install @agentvoice/sdk
export AGENTVOICE_API_KEY=avc_…Place a call and follow the transcript
createAndFollow places the call, streams state, partial words and finished turns, and resolves when the call ends. The brief tells the agent the goal and what it must refuse.
The agent can ask your app a question during the call. Answer it in onQuestion.
import { AgentVoice } from "@agentvoice/sdk";
const av = new AgentVoice({ apiKey: process.env.AGENTVOICE_API_KEY });
const call = await av.calls.createAndFollow(
{
line_id: "lin_main",
to: "+15615550123",
brief: {
goal: "Confirm the appointment on Friday at 3 PM, or move it to the next free slot.",
business: { name: "Palm Dental" },
limits: { refuse: ["pay a deposit"], may_commit_unattended: false },
},
},
{
onState: (c) => console.log(c.status, c.phase, c.talker),
onPartial: (p) => process.stdout.write(`\r${p.role}: ${p.text.slice(-100)}`),
onTurn: (t) => console.log(`\n${t.role}: ${t.text}`),
// The agent asks your app a question. Return the answer, or nothing to answer elsewhere.
onQuestion: (q) => q.options[0] ?? "Please use your best judgement.",
},
);
console.log(call.status, call.end_reason, call.outcome?.summary);Decide on incoming calls
Set a line to decide and your app gets a call.incoming event for each ring. Answer with the agent, a forward or a rejection before the deadline. If your app is slow, the fallback policy of the line runs.
// A line with inbound policy "decide": your code chooses before the deadline.
// When it does not, the line's fallback policy runs.
const follower = av.inbound.handle("lin_main", (incoming) => {
if (family.has(incoming.caller_e164)) return { mode: "forward", to: [owner] };
if (incoming.caller_e164.startsWith("+1800")) return { mode: "reject" };
return {
mode: "agent",
instructions: "Take a message and ask for a call back number.",
metadata: { source: "front desk" },
};
});
process.on("SIGINT", () => follower.stop());Give the agent tools
A tool is a name, a description and a JSON schema for its arguments. When the agent calls it, your app receives call.tool_call and posts the result back. A timeout keeps the call moving if your app does not answer.
// Create the tool once, then attach it to an agent or to a single call.
await av.tools.create({
name: "lookup_order",
description: "Look up the status of a customer order by its number.",
timeout_s: 15,
parameters: {
type: "object",
properties: { order_number: { type: "string" } },
required: ["order_number"],
},
});
// During a call the agent invokes it. Answer from your own code.
av.events.follow({
types: ["call.tool_call"],
on: {
"call.tool_call": async ({ data: { call_id, tool_call } }) => {
await av.calls.toolResult(call_id, {
tool_call_id: tool_call.id,
result: { status: "shipped", eta: "Tuesday" },
});
},
},
});Receive signed webhooks
Register a URL in the portal and pick the events you want. Each delivery carries an AgentVoice-Signature header with a timestamp and an HMAC SHA256 of the raw body. Reject signatures older than five minutes. Failed deliveries are retried and logged.
import { createServer } from "node:http";
import { AgentVoice, WEBHOOK_SIGNATURE_HEADER, WebhookSignatureError } from "@agentvoice/sdk";
const av = new AgentVoice({ apiKey: process.env.AGENTVOICE_API_KEY });
createServer(async (req, res) => {
const chunks: Buffer[] = [];
for await (const chunk of req) chunks.push(chunk as Buffer);
try {
// Verify the raw body. Re-serialised JSON will not match the signature.
const event = await av.webhooks.verify(
Buffer.concat(chunks),
req.headers[WEBHOOK_SIGNATURE_HEADER.toLowerCase()] as string | undefined,
process.env.WEBHOOK_SECRET!,
);
if (event.type === "call.ended") console.log(event.data.outcome?.summary);
res.writeHead(204).end();
} catch (err) {
res.writeHead(err instanceof WebhookSignatureError ? 400 : 500).end();
}
}).listen(8787);Listen in and take over
The portal console connects to the call room with a short lived grant. Ask for listen to hear both sides unseen, or talk to speak. A talking supervisor pauses the agent until you hand the call back.
curl -X POST https://agentvoice.top/v1/calls/$CALL_ID/supervise \
-H "Authorization: Bearer $AGENTVOICE_API_KEY" \
-d '{ "mode": "listen" }'
{ "url": "wss://…", "token": "…", "room": "…", "identity": "lis-…", "expires_at": "…" }Full reference
Every route, field and error code is in the OpenAPI reference. Get a key in the portal and place your first call.