Agents
An agent is a saved configure frame. Every setting a live call accepts is a setting an agent can carry, so there is no mapping layer between the two to drift out of sync.
Versions are immutable
Editing an agent never overwrites anything — it appends a version. Rollback is therefore publishing an older version rather than restoring a backup, and the history of what the agent used to say survives. A version is a few kilobytes of JSON, so this costs almost nothing and is the difference between knowing what changed last Tuesday and guessing.
Endpoints
| Method | Path | Does |
|---|---|---|
POST | /v1/agents | Create. Starts at version 1, published unless you say otherwise |
GET | /v1/agents | List yours. ?include_archived=true for the rest |
GET | /v1/agents/{id} | One agent. ?version=N for a specific version |
PATCH | /v1/agents/{id} | Save an edit as a new version |
GET | /v1/agents/{id}/versions | The history |
POST | /v1/agents/{id}/publish | Make a version live. This is rollback |
POST | /v1/agents/{id}/archive | Stop it answering. ?restore=true to undo |
GET | /v1/config-schema | Every setting, with type, default and group |
Three behaviours worth knowing before you integrate
Saving publishes by default
A draft-only default reads as safer and behaves worse: someone edits a prompt, hears no change on the next call, and concludes the product is broken. Pass publish: false to stage a change instead — drafts stay readable, so they are still testable.
The config is replaced, not merged
Merging would make a setting impossible to remove. Sending a config without allowed_languages would keep the old restriction forever, so an agent could never be un-restricted. Read the agent, change what you want, send the whole thing back.
An unknown setting is a 422 naming the setting
Silently dropping it would give someone who wrote systemPrompt an agent that ignores its prompt, with no indication why. The error lists the valid names.
The settings form builds itself
GET /v1/config-schema returns all 51 settings read out of the engine's own configuration object — names, types, defaults, groups and a note each. The dashboard renders its form from that response rather than from a copy, so a setting added to the engine appears in the product without anyone editing the dashboard. A setting the dashboard has no label for still appears, under its raw name, in an advanced section — which is the property that matters, because the alternative is a setting that exists in the engine and cannot be reached.
Three ways to start one
The blank settings form is the hardest screen here: fifty-one settings, and the one that decides whether the agent is any good is a prompt with no example of what a good one looks like. Someone used to writing for a chat window writes four polite sentences, which is unusable on a phone call, and nothing about the form says so. So there are three routes in, and they exist for different people rather than as variations on one.
Describe it in a sentence
POST /v1/agents/draft takes a plain-language brief and returns a config: a prompt written for a call, the languages, what counts as success, and what to read off the transcript afterwards. Nothing is created — you get a config to review and then post.
curl -X POST https://api.latency.cam/v1/agents/draft \
-H "Authorization: Bearer $LATENCY_KEY" \
-H "Content-Type: application/json" \
-d '{
"brief": "We are a courier in Pune. Ring customers whose parcel is out for delivery and check the address is right.",
"language": "hi"
}'interrupt_min_words is 3 because Indian callers backchannel with "haan" and "ji" constantly, semantic_turn is on because it can only ever shorten a turn, lock_voice is on because changing voice mid-call sounds like being handed to a different person. A model asked to fill in fifty-one fields produces plausible values for all of them and quietly undoes those decisions. It writes the parts that are about your business; the engine keeps the parts that are about how a phone call works.The output is validated against the same schema a hand-written config goes through before you ever see it, so a draft cannot come back as something that fails on save. Anything outside the permitted list is dropped and named in ignored.
Start from a template
The dashboard ships five: payment reminder, delivery helpdesk, abandoned cart, appointment reminder and lead qualification. Each is a complete working agent whose prompt is written for a phone line — short replies, one question per turn, no lists, an explicit opening line. None of them turns recording on, because that is not a decision a template should make for you.
Blank
Every setting at the engine default, which is already a working agent. Choose this when you already know what you are doing.
Create one directly
curl -X POST https://api.latency.cam/v1/agents \
-H "Authorization: Bearer $LATENCY_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "Delivery helpdesk",
"config": {
"language": "hi",
"allowed_languages": ["hi", "ta", "en"],
"system_prompt": "You are a delivery helpdesk for {{company}}. Reply in under 15 words.",
"variables": { "company": "Neureek" },
"goal": "Successful when the caller is told when their order arrives.",
"lock_voice": true,
"record_audio": false
}
}'Hearing it before you ship it
There are two ways to try an agent and they fail differently, which is why the dashboard offers both on the editing screen.
| Browser test call | Call a real phone | |
|---|---|---|
| Exercises | The engine: prompt, voice, knowledge, turn taking | All of that plus your carrier, the caller ID, the answer webhook, and a real 8kHz line |
| Uses | Your unsaved edits | The published version |
| Costs | Nothing | Your carrier's per-minute rate |
| Endpoint | wss://api.latency.cam/ws/stream | POST /v1/calls/outbound |
Choosing a voice
There are 38 and their names — "Tamil (Female)", "Hindi (Male)" — say nothing about how they sound, so the dashboard plays them. Each has a listen button that synthesises a sample through the real engine in that voice's own language, and you can type your own opening line to hear instead, which is usually what you actually want to compare. A voice is tied to its language: picking a Tamil voice does not make the agent speak Tamil, and switching language mid-call moves off that voice unless lock_voice is on.
Then talk to it: open it in the dashboard and use the call button, or point a websocket at it — see live calls.