Telroi API
Build voice, OTP, speech and CRM features on Telroi over one REST API.
Developer quickstart
Make your first API request in minutes. Send a voice OTP with a single call.
fetch("https://app.telroi.ai/v1/otp", {
method: "POST",
headers: {
"Authorization": "Bearer tlr_live_xxx",
"Content-Type": "application/json"
},
body: JSON.stringify({
"to": "+15551234567"
})
})
.then((res) => res.json())
.then((data) => console.log(data));v1Capabilities
Build voice verification, speech and calling into your product over one REST API.
Start building
Authentication
Authenticate every request with a secret API key in the Authorization header. Create keys in your dashboard under Developers.
curl https://app.telroi.ai/v1/numbers \
-H "Authorization: Bearer tlr_live_xxx"live tlr_live_ keys hit live infrastructure and bill your wallet. test tlr_test_ keys run in sandbox — calls are simulated, nothing is charged, and OTP code 000000 always verifies. Keep secret keys server-side.
Errors & rate limits
Telroi uses conventional HTTP status codes: 2xx success, 4xx a request problem, 5xx a Telroi-side error. Every error body has the same shape.
{
"error": {
"code": "unauthorized",
"message": "Invalid or revoked API key"
}
}HTTP status code summary
| Code | Description |
|---|---|
200 | OK — the request succeeded. |
201 | Created — a resource was created (e.g. an OTP was placed). |
400 | Bad Request — a parameter is missing or invalid. See the error code in the body. |
401 | Unauthorized — no valid API key was provided. Send it as a Bearer token. |
403 | Forbidden — the key is valid but lacks the required scope (e.g. otp:write). |
404 | Not Found — the resource or endpoint doesn't exist. |
405 | Method Not Allowed — wrong HTTP method for this endpoint. |
422 | Unprocessable Entity — the body is well-formed but failed validation. |
429 | Too Many Requests — a rate limit was exceeded. Check the retry guidance. |
5xx | Server Error — something went wrong on Telroi's end. These are rare. |
Error codes
When a request fails, the body contains an error.code you can branch on:
| Error code | HTTP | Meaning |
|---|---|---|
invalid | 400 | A required field is missing or malformed (e.g. a non-E.164 destination number). |
unauthorized | 401 | The API key is missing, revoked, or not recognized. |
forbidden | 403 | The key lacks the scope this endpoint needs (otp:write, speech:write, etc.). |
rate_limited | 429 | Too many OTP requests for this destination number. Respect the cooldown, hourly and daily caps; retry after the indicated delay. |
otp_failed | 502 | The OTP call could not be placed by the configured voice vendor. |
speech_failed | 502 | Text-to-speech or speech-to-text processing failed at the configured vendor. |
Rate limits
The Voice OTP endpoint is rate-limited per destination number by the operator policy (cooldown, hourly and daily caps). Exceeding a limit returns 429 with a rate_limited code.
Live Call widget
A button on your site that lets a visitor talk to you without dialling. They click, you get a call, and it rings your team like any other — because it is one.
Installing it
One script tag, before the closing body tag. The key identifies which workspace the call reaches and which route answers it.
<script src="https://app.telroi.ai/widget/v1.js"
data-telroi-key="wgt_your_key_here"></script>| Attributes | Description |
|---|---|
data-telroi-key | Required. Identifies your workspace and the route the call takes. |
data-telroi-launcher | Optional. Set to none to suppress the floating button when you raise the panel from your own control. Defaults to auto. |
Opening it yourself
The script attaches window.TelroiLiveCall once it has initialised. Call open() from your own button — a link in your navigation, a call-to-action mid-page — and the panel appears without a floating launcher.
<!-- Your own control, anywhere on the page -->
<button id="talk-to-us">Talk to us</button>
<script>
document.getElementById('talk-to-us').onclick = function () {
// Bound to a click rather than to page load: the method exists only once
// the widget script has initialised.
if (window.TelroiLiveCall && window.TelroiLiveCall.open) {
window.TelroiLiveCall.open();
}
};
</script>The method exists only after the script has loaded. Calling it from code that runs earlier will find nothing there, so bind it to a user action rather than to page load, or check for it first.
Knowing when a call starts
Set onCall before the script loads and it will be invoked when a session begins, with the session id, where it routed, and whether an AI voice answered. Useful for your own analytics.
<script>
// Set before the widget script loads, so it is there when a call begins.
window.TelroiLiveCall = { onCall: function (c) {
console.log(c.sessionId, c.routedTo, c.voice);
} };
</script>
<script src="https://app.telroi.ai/widget/v1.js"
data-telroi-key="wgt_your_key_here"
data-telroi-launcher="none"></script>How it appears
On a desktop the panel is a card of about 360px over a dimmed page. Below 640px it fills the screen instead, because a small rounded card on a handset leaves a strip of page around something somebody is trying to talk into. The size is decided each time the panel opens, so a rotated tablet gets the right shape.
The key is visible in your page source by design — it has to be, for the script to read it. It identifies a route rather than granting account access, but treat it as a public support line: anybody who copies it can place a call that rings your team and draws from your wallet.
