Speko Docs
Build

Multilingual agents

Author a greeting, a mailbox line, and workflow steps in every language one agent speaks.

Routing swaps an agent's STT, TTS, LLM and system prompt to match the caller's language. It cannot rewrite text a person wrote. A greeting, a voicemail line, and a workflow step's verbatim say are spoken as authored, in the language they were authored in. A correctly routed Spanish call still opens in English when English is the only greeting on file. Author that text per language instead.

Configure the languages

languageRouting.languages[] is the agent's language table. Each entry names a language and carries the text and provider pins that language needs.

curl -X PATCH https://api.speko.dev/v1/agents/$AGENT_ID \
  -H "Authorization: Bearer $SPEKO_API_KEY" \
  -H 'Content-Type: application/json' \
  -d '{
    "languageRouting": {
      "enabled": true,
      "languages": [
        {
          "language": "en",
          "firstMessage": "Hi, this is Alex from Iron Mike Delivery. Am I speaking with {{candidate_name}}?",
          "voicemailMessage": "Hi {{candidate_name}}, this is Alex from Iron Mike Delivery about the driver role. I will try again tomorrow."
        },
        {
          "language": "es",
          "allowedProviders": { "tts": ["elevenlabs"] },
          "voice": "QtY3JBOUKEB5xzrRfOKc",
          "firstMessage": "Hola, soy Alex de Iron Mike Delivery. Le llamo por el puesto de repartidor. ¿Hablo con {{candidate_name}}?",
          "voicemailMessage": "Hola {{candidate_name}}, soy Alex de Iron Mike Delivery. Le llamo mañana."
        }
      ]
    }
  }'

Up to four languages, each with a distinct primary subtag. Every text field is a Liquid template, compiled against the same variables as its agent-level counterpart. An unbound name is rejected at agent-write time with 400 MISSING_TEMPLATE_VARIABLES, not at session create.

Each entry has four text slots, mirroring the agent-level fields:

Wire nameSpoken on
firstMessageAny leg with no direction-specific text
inboundFirstMessageInbound calls
outboundFirstMessageOutbound calls
voicemailMessageA mailbox, under turnHandling.onMachine: 'leave_message'

The direction-specific greeting wins over firstMessage. Omit a slot to inherit the agent-level text. All three greetings accept an empty string, which is a real setting rather than a blank field: the call opens with silence and listens first. Only voicemailMessage rejects an empty string — a mailbox with silence recorded into it is not a policy.

Pin one language without arming the switcher

enabled gates switching only. The languages[] array doubles as the text table and is read either way, so an agent with enabled: false still speaks the authored text for whatever language a call is pinned to.

const call = await speko.voice.dial({
  to: '+34911234567',
  agentId: 'agent_123',
  intent: { language: 'es' },
});

That call greets from the es entry and leaves the es mailbox message, with no mid-call detection running and no chance of a stack switch mid-sentence. Set enabled: true when a caller may genuinely change language during the call; leave it false when each call knows its language up front and you want the greeting to match.

Translate a step that speaks

A workflow step's say goes straight to TTS with no model in the path, so an instruction to answer in Spanish cannot reach it. Give the step a translation:

{
  "id": "call_overview",
  "kind": "message",
  "say": "Let me explain how this call works: I'll tell you a little about Iron Mike Delivery.",
  "sayByLanguage": {
    "es": "Le explico cómo funciona esta llamada: le hablaré un poco de Iron Mike."
  },
  "instruction": "Confirm the candidate is still interested in the role.",
  "instructionByLanguage": {
    "es": "Confirme que el candidato sigue interesado en el puesto."
  }
}

Keys are bare primary subtags, so one es entry serves an es-419 session. Each field takes up to four variants; say variants cap at 600 characters and instruction variants at 2000. Variant selection happens before variable substitution, so a translated line still renders its {{variables}}.

Author these rather than translating at runtime. say holds the text that has to be exact: disclosures, consent language, prices. A machine translation of it reintroduces the drift say exists to prevent, in a language nobody on your team is reading back.

Language directives

Each routed language also gets a short "respond only in this language" line appended to its system prompt. That line is what stops the model answering in the prompt's own language once the stack has switched. It resolves for all 44 supported agent languages as of v0.0.505.

Earlier releases resolved it from a 24-entry table, so 23 languages got no directive at all. Swedish, Danish, Polish, Turkish, Hebrew, Ukrainian, Greek, Czech, Hungarian, Romanian, Slovak, Croatian, Bulgarian, Finnish and the Indic set were all in that gap. Nothing to configure: existing agents pick the directive up on their next call.

Next

On this page