> For the complete documentation index, see [llms.txt](https://docs.limecall.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.limecall.com/callback/javascript-api.md).

# JavaScript API

Drive the widget from your own code — open it, start a call, read its state, listen for events.

Once the widget is installed it exposes a global, `window.LimeCall`, so your own code can control it.

Everything here runs in the browser and needs no API key — the widget is already authenticated by the `data-key` in your snippet.

## Waiting until it is ready

`window.LimeCall` does not exist until the script has loaded, and the widget keeps initialising for a moment after that. Use the `ready` event:

```js
(function poll() {
  if (!window.LimeCall) return setTimeout(poll, 100);
  window.LimeCall.on("ready", function (state) {
    // safe to do anything here
  });
})();
```

Subscribing to `ready` **after** the widget is already ready still fires your handler, so there is no race to lose.

{% hint style="info" %}
Method calls made before the widget finishes initialising are queued and replayed once it is — up to **8** of them. Beyond that they are dropped. The catch is that a queued call returns `false` immediately, so never treat the return value as "it failed".
{% endhint %}

## Methods

| Method                           | Does                                                                    |
| -------------------------------- | ----------------------------------------------------------------------- |
| `open()`                         | Opens the widget panel.                                                 |
| `close()`                        | Closes it.                                                              |
| `toggle()`                       | Opens if closed, closes if open.                                        |
| `openTab(tab)`                   | Opens the panel on a tab: `"call"`, `"message"` or `"chat"`.            |
| `showLauncher()`                 | Shows the floating launcher button.                                     |
| `hideLauncher()`                 | Hides it — the widget still works through the API.                      |
| `call(phoneNumber)`              | Requests a callback to that number.                                     |
| `requestCallback(phone, fields)` | Requests a callback and **returns a promise** with the result.          |
| `schedule()`                     | Opens the scheduling view.                                              |
| `startWebCall()`                 | Starts a browser call.                                                  |
| `endWebCall()`                   | Ends it.                                                                |
| `hasAvailableAgents()`           | Promise resolving to whether anyone can actually take a call right now. |
| `setDepartment(label)`           | Routes subsequent requests to a department.                             |
| `getDepartments()`               | The departments configured on this widget.                              |
| `getState()`                     | Returns the widget's current state.                                     |
| `getVisitorId()`                 | This visitor's attribution id.                                          |
| `on(event, handler)`             | Subscribes to an event.                                                 |
| `off(event, handler)`            | Unsubscribes.                                                           |
| `version`                        | The loaded widget version.                                              |

### `openTab(tab)`

Only `"call"`, `"message"` and `"chat"` are accepted. Anything else returns `false` and does nothing.

### `call(phoneNumber)`

```js
window.LimeCall.call("+447700900123");
```

Trimmed and truncated to 32 characters; an empty value returns `false` without submitting.

**It does not tell you whether the callback was accepted.** Either listen for `callback:requested` and `callback:failed`, or use `requestCallback()` below, which returns a promise.

### `requestCallback(phone, fields)`

The same request, but you get the answer back:

```js
const result = await window.LimeCall.requestCallback("+447700900123", {
  name:  "Sam Okafor",
  email: "sam@example.com"
});

if (result.ok) {
  console.log("Callback id", result.id);   // poll or reconcile with this
} else {
  console.warn(result.message);            // show this to the visitor
}
```

Resolves to `{ ok, id, message }`. It never rejects — a network failure resolves as `{ ok: false, id: null, message: "network error" }`, so you do not need a `try`/`catch` around it.

The phone number is validated **before** any network call. It must be E.164 — a leading `+`, country code, 7–15 digits — and anything else resolves immediately with `ok: false` and a message saying so. Spaces, dashes and brackets are stripped for you, so `+44 7700 900123` is fine; `07700900123` is not, because there is no country code.

`name` is capped at 80 characters and `email` at 120. The department set through `setDepartment()` and the visitor's attribution id are attached automatically — you do not pass either.

{% hint style="info" %}
`id` is the same id the widget uses internally, so you can reconcile it against the `callback:requested` event or against calls in the REST API.
{% endhint %}

### `hasAvailableAgents()`

Resolves to `true` or `false`: can a call actually be taken right now?

```js
if (await window.LimeCall.hasAvailableAgents()) {
  showCallButton();
} else {
  showFormInstead();
}
```

The answer depends on how the widget routes:

| Routing                | Answers from                                                                                                                                    |
| ---------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------- |
| To a person or team    | Working hours, **and** whether the people it routes to still resolve. Someone who has left the account makes it `false`, not a silent dead end. |
| To the AI receptionist | Your remaining AI minutes. If you are out but have an overflow person configured, it is still `true` — a human answers.                         |

```mermaid
flowchart TD
    A["hasAvailableAgents()"] --> B{"How does the widget route?"}
    B -- "To a person or team" --> C{"Inside working hours?"}
    C -- "No" --> D["false"]
    C -- "Yes" --> E{"Do those people still resolve?"}
    E -- "No" --> D
    E -- "Yes" --> F["true"]
    B -- "To the AI receptionist" --> G{"AI minutes left?"}
    G -- "Yes" --> F
    G -- "No" --> H{"Overflow person configured?"}
    H -- "Yes" --> F
    H -- "No" --> D
```

Use it to decide whether to offer a call at all. Offering one that nobody picks up costs more goodwill than not offering it.

{% hint style="info" %}
The result is cached for 20 seconds, so a page that checks it repeatedly does not generate a request each time. Calling `setDepartment()` clears the cache, because availability can differ per department.
{% endhint %}

{% hint style="warning" %}
It is deliberately conservative on the client and optimistic on the server. No widget key, or a network failure, resolves `false` — so a broken page hides the call button rather than promising a call. But if the availability check itself errors server-side it answers `true`, matching what the call path does: an outage should not silently switch off inbound calls. Treat it as a strong signal, not a guarantee.
{% endhint %}

### `setDepartment(label)` and `getDepartments()`

If your widget has departments, you can route from code instead of making the visitor choose:

```js
window.LimeCall.on("ready", function () {
  console.log(window.LimeCall.getDepartments());   // ["Sales", "Support", "Billing"]

  if (location.pathname.startsWith("/pricing")) {
    window.LimeCall.setDepartment("Sales");
  }
});
```

`getDepartments()` returns the department names as plain strings. It reads them from the widget's loaded configuration, so **call it inside `ready`** — before that it returns an empty array.

`setDepartment(label)` applies to every request afterwards: the form, `call()`, `requestCallback()` and browser calls. It also moves the visible department picker, if one is rendered. Pass `""` or `null` to clear it.

{% hint style="info" %}
If the visitor picks a department themselves, **their choice wins.** `setDepartment()` sets the default, not an override — which is what you want, since they know why they are calling better than the page does.
{% endhint %}

A label that does not match a configured department resets the visible picker to "Any department", and routing falls back to your default. Match the names exactly as they appear in `getDepartments()`.

### `getVisitorId()`

Returns the attribution id the widget assigns this visitor, or `null` before it is ready:

```js
analytics.identify({ limecall_visitor_id: window.LimeCall.getVisitorId() });
```

It is the same id carried on callback requests, so it is how you stitch a widget conversion to a session in your own analytics.

### `getState()`

```js
var s = window.LimeCall.getState();
// { ready: true, open: false, mode: "bubble", inWebCall: false, tab: null, visible: true }
```

| Key         | Meaning                                                            |
| ----------- | ------------------------------------------------------------------ |
| `ready`     | The widget has finished initialising.                              |
| `open`      | The panel is open.                                                 |
| `mode`      | Display style: `inline`, `bubble`, `side` or `popup`.              |
| `inWebCall` | A browser call is in progress.                                     |
| `tab`       | The tab currently shown, or `null`.                                |
| `visible`   | The widget is rendered on this page — respects your display rules. |

`visible` is the one to check before wiring your own button to `open()`: a display rule may have hidden the widget on this page entirely.

## Events

Subscribe with `on(event, handler)`, unsubscribe with `off(event, handler)`.

| Event                | Fires when                        | Payload                                                                                         |
| -------------------- | --------------------------------- | ----------------------------------------------------------------------------------------------- |
| `ready`              | The widget has initialised.       | `{ visible, mode }`                                                                             |
| `open`               | The panel opens.                  | `{ mode }`                                                                                      |
| `close`              | The panel closes.                 | `{ mode }`                                                                                      |
| `tab`                | The visitor switches tab.         | `{ tab }`                                                                                       |
| `teaser`             | The teaser prompt is shown.       | `{ text }`                                                                                      |
| `score`              | The visitor's lead score changes. | `{ points, total, threshold, rule }`                                                            |
| `callback:requested` | A callback was accepted.          | `{ phone, mode, id }` — `mode` is `"now"` or `"schedule"`; `scheduledAt` present when scheduled |
| `callback:failed`    | A request was rejected or failed. | `{ reason, phone, message }` — `reason` is `"rejected"` or `"network"`                          |
| `message:sent`       | The visitor sent a message.       | —                                                                                               |
| `webcall:started`    | A browser call began.             | —                                                                                               |
| `webcall:ended`      | A browser call ended.             | —                                                                                               |

Unknown event names are rejected: `on()` returns `false` rather than silently registering a handler that never fires.

### Tracking a conversion

`callback:requested` fires only once the request was accepted, so it does not over-count:

```js
window.LimeCall.on("callback:requested", function (e) {
  gtag("event", "generate_lead", {
    method: "limecall_widget",
    mode: e.mode,
    callback_id: e.id
  });
});

window.LimeCall.on("callback:failed", function (e) {
  console.warn("Callback failed:", e.reason, e.message);
});
```

Wire `callback:failed` too. A silent rejection — closed hours, a blocked number, an unsupported country — otherwise looks exactly like a visitor who changed their mind.

### The `score` event

The widget scores visitor engagement and fires `score` as it changes, with the running `total` and the `threshold` it is working toward. Use it to trigger your own behaviour — reveal an offer, or open the widget — when someone is clearly engaged:

```js
window.LimeCall.on("score", function (e) {
  if (e.total >= e.threshold) window.LimeCall.open();
});
```

## Opening from your own button

```html
<button type="button" id="call-me">Request a callback</button>

<script>
window.LimeCall && window.LimeCall.on("ready", function () {
  window.LimeCall.hideLauncher();
  document.getElementById("call-me").addEventListener("click", function () {
    window.LimeCall.open();
  });
});
</script>
```

## Opening from a link, with no code

Any link to `#limecall` opens the widget when the page loads:

```html
<a href="#limecall">Request a callback</a>
```

`#lc-open` and `#lc-widget` do the same thing. They work across pages too, which is the useful part — put `https://example.com/pricing#limecall` in an email, an ad or a QR code and the widget opens on arrival, with nothing to install on the page beyond the usual snippet.

{% hint style="info" %}
The link opens the widget on load and whenever the address changes. If a visitor closes the widget and clicks the **same** link again, the address has not changed, so nothing happens — the browser fires no event. For a button the visitor may use more than once, call `open()` in a click handler instead.
{% endhint %}

## Next

* [Connect your own form](/callback/connect-your-own-form.md) — fire a callback from a form you already have
* [Widget recipes](/callback/widget-recipes.md) — copy-paste patterns
* [Testing & debugging](/callback/testing-and-debugging.md)
