# Texa Desk developers Source: https://developers.texadesk.com/ Texa Desk is one system between a business and its customers: a helpdesk on WhatsApp, email, phone, SMS, live chat and social, with **Texa AI**, an agent that answers, looks things up and acts in your systems, and hands over to your team when it should. Everything you can do on screen you can also do from code. These docs cover every way in: the APIs, the webhooks, the widgets for your website and app, the SDKs, and how Texa connects to the systems you already run. ## Start here ## What you can build | You want to | Use | Start with | |---|---|---| | Put Texa AI in your own app, website or backend | The **Texa API** at `https://api.texadesk.com/v1` | [Quickstart](/quickstart) | | A chat in your website or app that knows the signed-in customer | `widget.js`, `@texadesk/react-native` | [Your website and app](/guides/website-and-app) | | Answer customers on your own WhatsApp integration | The Texa API | [A WhatsApp bot](/guides/whatsapp-bot) | | Let customers talk to Texa AI by voice in your app | Voice sessions | [Voice in your app](/guides/voice) | | Let Texa AI check a payment, an order or an account | Actions and connectors | [Actions](/guides/actions) | | Read and change conversations, contacts and tickets in Texa Desk | The **Texa Desk API** at `https://desk.texadesk.com/api/v1` | [Texa Desk API](/api-reference/desk) | | Bring a channel Texa Desk doesn't have | An API inbox and the Client API | [Your own channel](/desk/api-channel) | | Your own bot, or a panel inside each conversation | Agent bots, dashboard apps | [Agent bots](/desk/agent-bots) | | Show customers their requests in your own app | The customer portal API | [Customer portal](/desk/customer-portal) | | Hear about everything as it happens | Webhooks, from the Texa API and from Texa Desk | [Webhooks](/guides/webhooks) | ## The two APIs Texa has two APIs, because it has two jobs. - **The Texa API** (`https://api.texadesk.com/v1`) is Texa AI as a service. Your server opens a conversation, sends each customer message and gets Texa AI's reply, with its actions and handovers, as events. Keys are `texa_test_…` (the sandbox) and `texa_live_…`. [More](/api-reference/texa-api) - **The Texa Desk API** (`https://desk.texadesk.com/api/v1`) is the helpdesk: inboxes, conversations, messages, contacts, teams, tickets, reports and settings. It acts as a person in your team, with their access token. [More](/api-reference/desk) They meet in one place: a conversation Texa AI hands over appears in Texa Desk for your team, and what your team writes there comes back to your app. ## Where things are | | Address | |---|---| | Texa API | `https://api.texadesk.com/v1` | | Texa API's OpenAPI document | [https://api.texadesk.com/v1/openapi.json](https://api.texadesk.com/v1/openapi.json) | | Texa Desk (the app and its API) | `https://desk.texadesk.com` | | Texa Desk's OpenAPI document | [/openapi/texa-desk.json](/openapi/texa-desk.json) | | Webhooks into Texa (connectors' replies) | `https://hooks.texadesk.com` | | Voice sessions | `wss://voice.texadesk.com` | | Status | [https://api.texadesk.com/status](https://api.texadesk.com/status) | | For AI coding tools | [/llms.txt](/llms.txt) and [/llms-full.txt](/llms-full.txt) | ## Help - **Your keys and settings** are in Texa Desk, under **Texa AI → Developers**. - **Something not working?** Every error carries a request id in the `X-Request-Id` header. Send it with your question to [info@texadesk.com](mailto:info@texadesk.com) or through the chat at [texadesk.com](https://texadesk.com). - **Using Texa Desk itself?** The [help centre](https://texadesk.com/help) is written for your team. --- # Quickstart Source: https://developers.texadesk.com/quickstart This takes about five minutes. You need a Texa Desk account (sign up at [texadesk.com](https://texadesk.com)) where you are an administrator, and a terminal. ## 1. Make a sandbox key In Texa Desk, open **Texa AI → Developers → API keys** and choose **New key**: - **Environment:** Sandbox. A sandbox key (`texa_test_…`) reaches the same agent, knowledge and approved procedures as live, but nothing reaches a real customer and actions are answered by [fixtures](/guides/sandbox) you set. - **Name:** something you'll recognise in the request log, such as "Laptop". The key is shown **once**. Copy it into your shell: ```bash export TEXA_API_KEY=texa_test_... ``` Keep keys on your server. Never put one in an app or a web page: anyone with it can act as your business. (For apps and pages there are [widget sessions](/guides/website-and-app).) ## 2. Check the key ```bash curl https://api.texadesk.com/v1/me -H "Authorization: Bearer $TEXA_API_KEY" ``` The answer names your account and says `"environment": "test"`. ## 3. Open a conversation ```bash cURL curl https://api.texadesk.com/v1/conversations \ -H "Authorization: Bearer $TEXA_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: quickstart-user-1842" \ -d '{"channel": "web", "customer_ref": "user-1842", "first_name": "Tolu"}' ``` ```python Python # pip install texa from texa import Client texa = Client() # reads TEXA_API_KEY convo = texa.open_conversation(channel="web", customer_ref="user-1842", first_name="Tolu", idempotency_key="quickstart-user-1842") print(convo["conversation_id"]) ``` ```js JavaScript // npm install @texadesk/sdk import { TexaClient } from "@texadesk/sdk"; const texa = new TexaClient({ key: process.env.TEXA_API_KEY! }); const convo = await texa.openConversation( { channel: "web", customer_ref: "user-1842", first_name: "Tolu" }, "quickstart-user-1842"); console.log(convo.conversation_id); ``` ```json {"conversation_id": "cnv_8a7f0c...", "state": "active", "events": []} ``` `customer_ref` is **your** id for the customer. Texa AI remembers a customer across conversations by it, so use the same one every time. ## 4. Send the customer's message ```bash cURL curl https://api.texadesk.com/v1/conversations/cnv_8a7f0c.../messages \ -H "Authorization: Bearer $TEXA_API_KEY" \ -H "Content-Type: application/json" \ -d '{"message_id": "msg-1", "text": "Hello, what are your opening hours?"}' ``` ```python Python out = texa.send_message(convo["conversation_id"], "Hello, what are your opening hours?", message_id="msg-1") for e in out["events"]: if e["type"] == "reply": print(e["text"]) ``` ```js JavaScript const out = await texa.sendMessage(convo.conversation_id, { text: "Hello, what are your opening hours?", message_id: "msg-1" }); for (const e of out.events) if (e.type === "reply") console.log(e.text); ``` ```json { "conversation_id": "cnv_8a7f0c...", "state": "active", "events": [ {"type": "reply", "reply_id": "rpl_...", "format": "chat", "text": "We're open Monday to Friday, 8:00 to 18:00, and Saturday 9:00 to 14:00."} ] } ``` Texa AI answers from the knowledge your team approved in Texa Desk (**Texa AI → Knowledge**). If it has no knowledge yet, it says it doesn't know rather than making something up. `message_id` is your own id for the message. Send the same one again (after a timeout, say) and you get the first answer back, marked `"replayed": true`: nothing runs twice. ## 5. Read the events Every answer carries `events`, in order. Show each `reply` to the customer. The others tell you what else happened: | Event | What to do | |---|---| | `reply` | Show `text` to the customer | | `action_request` | Texa AI needs something from your systems. See [actions](/guides/actions) | | `handover` | People take over now; Texa AI stops replying. See [handing over](/guides/handover) | | `close` | The conversation is over | ## 6. Look at it in Texa Desk Open **Texa AI → Developers → Request log** to see each request you made, its status and its request id. With handover to Texa Desk switched on (the default), conversations that need a person appear in your **App conversations** inbox. ## Next --- # Products and how they fit Source: https://developers.texadesk.com/products ## The parts | Product | What it is | What you build with it | |---|---|---| | **Texa Desk** | The helpdesk your team works in, on the web at `https://desk.texadesk.com` and in the iPhone and Android app. Inboxes for WhatsApp, email, phone, SMS, live chat, Facebook, Instagram, Telegram and more; contacts, teams, labels, tickets, the help centre, campaigns and reports | The [Texa Desk API](/api-reference/desk), its [webhooks](/guides/desk-webhooks), [agent bots](/desk/agent-bots), [dashboard apps](/desk/dashboard-apps), [your own channel](/desk/api-channel) | | **Texa AI** | The agent that answers customers on every channel, follows the procedures your team approved, runs actions in your systems and hands over to people. Set up in Texa Desk under **Texa AI** | Everything below reaches it | | **The Texa API** | Texa AI as a service for your own software, at `https://api.texadesk.com/v1` | [Conversations, events, actions, voice](/api-reference/texa-api) | | **The website widget and app components** | Texa AI as a chat in your website (`widget.js`) and app (`@texadesk/react-native`, Flutter soon), for visitors and signed-in customers | [Your website and app](/guides/website-and-app) | | **Live chat** | Texa Desk's own chat bubble for your website, answered by your team and Texa AI | [Live chat](/desk/live-chat), the [WordPress plugin](/desk/wordpress) | | **Connectors** | Texa AI's way into your systems: ready-made (Paystack, Flutterwave, Shopify…), your API, your server, MCP | [Actions](/guides/actions), [connectors](/guides/connectors) | | **The Texa Relay** | A small program that runs inside a bank's network and answers Texa AI's lookups without data or credentials leaving it | [The Texa Relay](/guides/relay) | | **The customer portal** | Your help centre's "My requests": customers follow and answer their tickets, on the web or in your app | [Customer portal](/desk/customer-portal) | | **SDKs** | Python, JavaScript and TypeScript, React Native, Go, Swift and PHP | [SDKs](/sdks) | ## How a message flows ```text customer ─▶ WhatsApp / email / phone / live chat ─▶ Texa Desk inbox │ Texa AI answers ─┤─▶ actions in your systems │ (connectors, your server, the Relay) needs a person? ─────┴─▶ your team, in Texa Desk your app ─▶ Texa API (or the widget) ─▶ Texa AI ─▶ events back to your app and webhooks └── handover ─▶ "App conversations" inbox in Texa Desk ``` - **Customers who reach you on a channel** (WhatsApp, email, a call) land in a Texa Desk inbox. Texa AI answers there if it's on for that inbox. You extend this with the Texa Desk API and webhooks. - **Customers who use your own app or website** reach Texa AI through the Texa API or the widget. Your app shows the replies. When a person is needed, the conversation appears in Texa Desk. - **Either way, actions** go to your systems the same way, and every conversation is in one place for your team. ## Which one do I use? - *"I want Texa AI to answer inside my app."* The [widget or app component](/guides/website-and-app) if you want our chat; the [Texa API](/quickstart) if you're drawing your own. - *"I want to read every conversation into my data warehouse."* [Texa Desk webhooks](/guides/desk-webhooks) and the [Application API](/api-reference/desk). - *"I want Texa AI to check a customer's transfer."* An [action](/guides/actions), through a [connector](/guides/connectors) or your own server. - *"Our data can't leave our network."* The [Texa Relay](/guides/relay), and a [dedicated instance](/hosting/dedicated) if the whole system must be in your country. - *"We already use Zendesk."* [Texa AI inside your helpdesk](/desk/other-helpdesks). --- # Concepts Source: https://developers.texadesk.com/concepts ## Account A business on Texa Desk. Everything (inboxes, people, Texa AI's settings, keys, credits) belongs to one account. The Texa Desk API puts it in the path (`/api/v1/accounts/{account_id}`); the Texa API knows it from the key. ## Sandbox and live Every account has two environments for Texa AI: | | Sandbox | Live | |---|---|---| | Key | `texa_test_…` | `texa_live_…` | | Customers | none: you play them | real | | Actions | answered by [fixtures](/guides/sandbox), or your staging connectors | your systems | | Webhooks | sandbox endpoints (`http` allowed) | live endpoints (`https` only) | | Credits | counted separately as `sandbox_requests` | drawn from your prepaid balance | The same procedures and knowledge serve both, so what you test is what customers get. ## Inbox and channel An **inbox** is where one channel's conversations arrive in Texa Desk: a WhatsApp number, an email address, a website's live chat, a phone line, an API inbox. Your team members belong to inboxes, and Texa AI is switched on per inbox. Conversations from the Texa API arrive in the **App conversations** inbox when they are handed over. ## Conversation One exchange with a customer. There are two kinds of id, because there are two systems: - **In Texa Desk**, a conversation has a number shown to your team (its *display id*, `#171`), used in the Texa Desk API's paths. - **In the Texa API**, a conversation is `cnv_…`. When a Texa API conversation is handed over to Texa Desk, it gets a Desk conversation too. A Texa API conversation is `active`, with **people** (handed over), or `closed`. ## Customer and contact - A **contact** is a person in Texa Desk, with a name, phone, email and custom attributes. - A **customer reference** (`customer_ref`) is *your* id for a person in the Texa API, such as `user-1842`. Texa AI remembers a customer across conversations by it. [Merge two references](/api-reference/texa-api/customers/two-references-one-person-merge-them) or [forget a customer](/guides/customers) at any time. ## Texa AI, procedures and knowledge - **Knowledge** is what Texa AI answers from: documents, web pages and help centre articles your team added and approved in **Texa AI → Knowledge**. - **Procedures** are your written instructions for one kind of problem ("a failed transfer"), in plain words, compiled, checked and approved by a named person. Texa AI follows them step by step. - **The agent** is Texa AI's name, voice and manner for your business (`GET /v1/agent`). - **The kill switch** pauses Texa AI for the whole account at once (`GET /v1/kill-switch` tells you whether it's paused). ## Action Something Texa AI asks your systems to do: look up a transfer, cancel an order, issue a refund. An action has a name, typed inputs and outputs, and rules: whether it changes something, whether the customer must say yes, whether they must be verified first. Texa AI uses an action only in a step of an approved procedure. [Actions](/guides/actions) ## Event Everything that happens in a Texa API conversation is an event: a `reply`, an `action_request`, a `handover`, a `close`. Events come back in the answer to your request, are all on `GET /v1/events` in order, and are pushed to your [webhooks](/guides/webhooks). ## Handover Texa AI passing a conversation to people, with a summary and the facts it established. From then on it does not reply until people hand the conversation back. [Handing over](/guides/handover) ## Ticket A customer's request that needs tracking beyond one conversation: a reference (`AUD-1001`), a type, a state, a promise ("resolved within 14 days"), and every conversation about it. [Tickets](/desk/tickets) ## Credits Texa Desk is prepaid. Texa AI's replies, actions, voice minutes and API requests draw down your account's credit balance, the same whether they come through Texa Desk or the API. When it runs out, the Texa API answers `402`. Top up in Texa Desk under **Texa AI → Credits and billing**. [Usage and credits](/api-basics/usage) --- # Authentication Source: https://developers.texadesk.com/authentication Texa has a few kinds of credential because they live in different places: your server, a web page, an app, a webhook. Use the one meant for where your code runs. ## At a glance | Credential | Looks like | Where you get it | Where it may live | Reaches | |---|---|---|---|---| | Texa API key, sandbox | `texa_test_…` | Texa AI → Developers → API keys | your server only | your sandbox | | Texa API key, live | `texa_live_…` | the same | your server only | your live account | | Site key | `pk_live_…`, `pk_test_…` | Texa AI → Developers → Website and app | a web page or app | visitor chats, only from your listed websites | | Widget session token | `wst_…` | your server, `POST /v1/widget-sessions` | the page or app, for one customer | that customer's conversations, for up to a few hours | | Voice session token | one-time | your server, `POST /v1/conversations/{id}/voice-sessions` | the app, once | one voice session | | Texa Desk access token | a long random string | Texa Desk → Profile settings → Access token | your server | the Texa Desk API, as that person | | Agent bot token | a long random string | when you create an agent bot | your bot's server | the conversations of the bot's inboxes | | Live chat HMAC key | per website inbox | the inbox → Configuration → Identity validation | your server | signs `identifier_hash` for live chat | | Webhook signing secret | per endpoint | when you add the endpoint | your server | checks that a request came from Texa | | Platform app token | a long random string | a dedicated instance's super admin | your server | the [Platform API](/api-reference/platform), dedicated instances only | ## Texa API keys Send the key as a bearer token on every request: ```http Authorization: Bearer texa_live_... ``` - A key is shown **once**, when you make it. Texa keeps only a hash of it. - **Revoke** a key in Texa AI → Developers → API keys. It stops working at once. - Make **one key per system** (your backend, your WhatsApp bridge, a test laptop): the request log shows which key did what, and revoking one leaves the others working. - A key never belongs in an app or a web page. For those, your server makes a [widget session](/guides/website-and-app#2-signed-in-customers-your-server-says-who-they-are) or a [voice session](/guides/voice). ## Site keys and widget sessions A **site key** is safe to put in a web page: it only opens *visitor* chats, only from the websites you list under **Websites your widget runs on** (the sandbox site key works anywhere), and at most 20 an hour per address. A visitor is never treated as verified. For a signed-in customer, **your server** calls `POST /v1/widget-sessions` with its own key and the customer's reference, and hands the page the `token`. The token reaches only that customer's conversations, and expires (`ttl_s`, one hour by default). ## Texa Desk access tokens Every person in Texa Desk has an access token under **Profile settings → Access token**. Send it in the `api_access_token` header: ```bash curl https://desk.texadesk.com/api/v1/accounts/1/conversations \ -H "api_access_token: $TEXA_DESK_TOKEN" ``` The token acts as that person, with their role's permissions and only the inboxes and teams they see. For an integration, add a dedicated person (an administrator or a custom role with only what the integration needs) rather than using someone's own token, and reset it from their profile if it leaks. **Agent bots** have their own token, sent the same way, which works only on the conversations of the inboxes the bot is connected to. [Agent bots](/desk/agent-bots) ## The Client API The [Client API](/api-reference/client) builds a customer-facing chat on an API inbox. It needs no secret: an inbox identifier (from the inbox's settings) and a contact identifier (returned when you create the contact). Treat the contact identifier as that customer's own secret: whoever has it can read their conversations. Turn on **identity validation** on the inbox so only your server can create contacts for known users. ## Checking what Texa sends you Everything Texa sends your server is signed with HMAC-SHA256: Texa API webhooks, action requests to your server, Texa Desk webhooks. Check the signature over the raw body and reject old timestamps. ```python Python import hashlib, hmac, time def from_texa(secret: str, timestamp: str, raw_body: bytes, signature: str) -> bool: if abs(time.time() - int(timestamp)) > 300: return False expected = "sha256=" + hmac.new(secret.encode(), timestamp.encode() + b"." + raw_body, hashlib.sha256).hexdigest() return hmac.compare_digest(expected, signature) ``` ```js Node import crypto from "node:crypto"; export function fromTexa(secret, timestamp, rawBody, signature) { if (Math.abs(Date.now() / 1000 - Number(timestamp)) > 300) return false; const expected = "sha256=" + crypto.createHmac("sha256", secret) .update(`${timestamp}.`).update(rawBody).digest("hex"); return signature.length === expected.length && crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expected)); } ``` ```php PHP function from_texa(string $secret, string $timestamp, string $rawBody, string $signature): bool { if (abs(time() - (int) $timestamp) > 300) return false; $expected = 'sha256=' . hash_hmac('sha256', $timestamp . '.' . $rawBody, $secret); return hash_equals($expected, $signature); } ``` | Sent by | Timestamp header | Signature header | |---|---|---| | Texa API webhooks and action requests | `X-Texa-Timestamp` | `X-Texa-Signature` | | Texa Desk webhooks | `X-Chatwoot-Timestamp` | `X-Chatwoot-Signature` | Texa Desk's webhook headers keep the names of the open-source helpdesk it is built on, so existing integrations keep working. Every SDK has a webhook check built in. --- # Errors Source: https://developers.texadesk.com/api-basics/errors Every error from the Texa API has the same shape, and the request's id is in the `X-Request-Id` header. Quote it to Texa support and we can find the request at once. ```json {"error": {"type": "invalid_request_error", "code": "invalid", "message": "a message has text", "field": "text"}} ``` | Field | Meaning | |---|---| | `type` | the family of error, below | | `code` | a stable machine-readable reason, such as `invalid`, `not_found`, `conversation_closed`, `rate_limited`, `insufficient_credits` | | `message` | a sentence for a developer. Don't show it to customers | | `field` | for a request that isn't valid, the part that's wrong | ## Statuses | Status | Meaning | Retry? | |---|---|---| | 400, 422 | The request is not valid. `field` names the part | No: fix the request | | 401 | The key is missing, unknown or revoked | No | | 402 | Your prepaid credits are used up. Top up in Texa Desk under **Texa AI → Credits and billing** | After topping up | | 403 | The key can't do this: a sandbox route with a live key, a suspended account | No | | 404 | No such route, or nothing with that id in your account | No | | 409 | The conversation's state doesn't allow it (closed, or with a person), or an `Idempotency-Key` was reused with a different body | No: read `code` | | 429 | Too many requests. Wait `Retry-After` seconds | Yes, after the wait | | 500, 502, 503 | Our side | Yes, with the same `Idempotency-Key` or `message_id` | ## Codes worth handling | `code` | When | What to do | |---|---|---| | `conversation_closed` | You sent a message to a closed conversation | Open a new conversation for the customer and send it there ([example](/guides/whatsapp-bot)) | | `rate_limited` | Over your requests per minute | Wait `Retry-After` seconds | | `idempotency_key_reused` | The same `Idempotency-Key` with a different body | Use a new key for a new request | | `idempotency_in_progress` | A retry arrived while the first request with that key is still running | Wait a moment and retry | | `invalid_api_key` | The key is missing, unknown or revoked | Check the key, or make a new one | | `insufficient_credits` | The prepaid balance is used up | Top up in Texa Desk | | `test_key_required` | A sandbox route (fixtures) with a live key | Use a `texa_test_…` key | | `account_suspended` | The account is suspended | Talk to Texa support | | `too_large` | An upload over the size limit | Send a smaller file | | `not_found` | Nothing with that id in this account and environment | Check you're using the right key: sandbox and live don't share conversations | New codes may be added. Treat an unknown code by its status. ## Texa Desk API errors The Texa Desk API answers in its own shape: `{"error": "..."}` or `{"message": "...", "errors": [...]}`, with `401` for a missing or wrong `api_access_token`, `404` for something outside the person's account or view, and `422` for a refused change, with a sentence you can show as it is. ## Failures Texa AI meets An error in *your* systems (an action that fails or times out) is not an API error. Texa AI tells the customer honestly that it couldn't check and offers a person, and the action's result is in the events. See [actions](/guides/actions). --- # Rate limits Source: https://developers.texadesk.com/api-basics/rate-limits Each account has a number of Texa API requests per minute for each environment. By default: | Environment | Requests per minute | |---|---| | Live | 300 | | Sandbox | 60 | Every answer says where you stand: | Header | Meaning | |---|---| | `X-RateLimit-Limit` | requests allowed in the current minute | | `X-RateLimit-Remaining` | requests left in it | | `X-RateLimit-Reset` | when the minute ends (Unix seconds) | Over the limit, the answer is **429** with `Retry-After` in seconds. Wait that long, then send the same request again (with the same `Idempotency-Key`, if it had one). - `GET /v1/me` shows your current limit as `limits.requests_per_minute`. - The widget's polling for new lines doesn't count against your limit. - Need more? Write to [info@texadesk.com](mailto:info@texadesk.com) with your expected traffic. Limits are raised per account. ## Texa Desk API The Texa Desk API limits requests per person and per address to protect the helpdesk your team is working in. Space out bulk jobs (a few requests a second), and back off on 429. --- # Retries and idempotency Source: https://developers.texadesk.com/api-basics/idempotency Networks fail. Texa is built so you can always send a request again without anything happening twice. ## Idempotency-Key Any `POST` to the Texa API can carry an `Idempotency-Key` header: a string you choose, unique for the thing you're doing. ```bash curl https://api.texadesk.com/v1/conversations \ -H "Authorization: Bearer $TEXA_API_KEY" \ -H "Idempotency-Key: open-user-1842-2026-10-19" \ -H "Content-Type: application/json" \ -d '{"channel": "web", "customer_ref": "user-1842"}' ``` - The first request runs. - A retry with the **same key and the same body** gets the same answer back, with the header `Idempotent-Replayed: true`. Nothing runs again. - The same key with a **different body** is a `409`: you've reused a key by mistake. - Keys are kept for **24 hours**. Good keys say what they're for: `open-wa-2348031234567`, `refund-order-88-attempt-1`. ## message_id Messages also take your own `message_id`, which works for as long as the conversation lasts. Send the same `message_id` again and you get the first answer, marked `"replayed": true`. If your channel gives each message an id (WhatsApp does), use it. ## When to retry | Answer | Retry? | |---|---| | A timeout or a dropped connection | Yes, with the same key or `message_id` | | `429` | Yes, after `Retry-After` seconds | | `5xx` | Yes, after a pause that doubles each time (1 s, 2 s, 4 s…), a few times | | `4xx` other than 429 | No: the request itself needs fixing | No SDK retries behind your back: you decide when, and they make it safe by sending the same key. ## What Texa sends you The other direction works the same way. Action requests carry an `idempotency_key` for your payment or ledger API, and webhooks carry `X-Texa-Event-Id`. Store what you've handled, and ignore a repeat. --- # Lists and the event feed Source: https://developers.texadesk.com/api-basics/pagination ## Cursors Texa API lists page with a cursor, never an offset. An answer gives you `next`; pass it as `after` to get the page after it. `has_more` says whether there is one. ```bash curl "https://api.texadesk.com/v1/events?limit=100" -H "Authorization: Bearer $TEXA_API_KEY" ``` ```json {"events": [{"id": "evt_431", "type": "conversation.reply", "...": "..."}], "next": "evt_530", "has_more": true} ``` ```bash curl "https://api.texadesk.com/v1/events?after=evt_530&limit=100" -H "Authorization: Bearer $TEXA_API_KEY" ``` `limit` is at most 1,000 for events. The SDKs page for you: ```python Python for event in texa.events(after=saved_cursor): handle(event) saved_cursor = event["id"] ``` ```js JavaScript let after = savedCursor; for (;;) { const page = await texa.eventsPage(after, 100); for (const e of page.events) handle(e); after = page.next; if (!page.has_more) break; } ``` ## The event feed `GET /v1/events` holds every event of every conversation in your account, in order: replies, action requests, handovers, closes, calls, media, quality scores. It is the **complete record**. Webhooks push the same events, but if your server was down, read the feed from your last cursor instead of waiting for retries. - `types` takes a comma-separated list (`conversation.reply,conversation.handover`) for only those. - Keep the last `id` you handled; it's your cursor. - Events are the same in the feed and in webhooks, except that webhooks add `environment`. ## Texa Desk API lists The Texa Desk API pages by number: `page=2`, with `meta` giving counts. Each endpoint in the [reference](/api-reference/desk) says its page size. --- # Usage and credits Source: https://developers.texadesk.com/api-basics/usage Texa Desk is prepaid, like a phone's airtime: your account holds a balance of credits, and what Texa AI does draws it down. The API counts exactly as Texa Desk does, so a reply costs the same whether it went out on WhatsApp or through your app. ## What is counted | Meter | Counts | |---|---| | `conversations` | conversations opened with Texa AI in the loop | | `messages_in` | customer messages Texa AI read | | `ai_replies` | Texa AI's messages sent to customers | | `ai_drafts` | Texa AI's drafts shown to people (suggest reply, shadow mode) | | `handovers` | conversations Texa AI handed to people | | `ai_resolutions` | conversations Texa AI closed as resolved without a person | | `actions` | action requests run in your systems | | `media_items`, `media_seconds` | voice notes, photos, documents and videos understood; seconds of audio and video transcribed | | `voice_minutes` | minutes of Texa AI voice, on the phone and in your app | | `sms_segments_out`, `sms_in` | SMS sent and received | | `whatsapp_templates` | WhatsApp template messages sent | | `campaign_sends` | campaign messages and calls | | `research_responses` | completed surveys and interviews | | `ask_questions` | questions put to Ask AI, including through [MCP](/guides/mcp) | | `api_requests` | Texa API requests with a live key | | `sandbox_requests` | Texa API requests with a test key: **free, up to 2,000 an account a month** | Prices per meter are on [texadesk.com/pricing](https://texadesk.com/pricing) and in Texa Desk under **Texa AI → Credits and billing**. The sandbox is free so you can build without paying: test keys make up to 2,000 requests an account a month at no charge. Over that, they answer `402` until the next month (ask Texa if you need more while you build). ## This month's usage ```bash curl https://api.texadesk.com/v1/usage -H "Authorization: Bearer $TEXA_API_KEY" ``` ```json {"month": "2026-10", "meters": [ {"meter": "conversations", "description": "conversations opened with the AI in the loop", "quantity": 412}, {"meter": "ai_replies", "description": "AI messages sent to customers", "quantity": 1830} ]} ``` Pass `month=2026-09` for an earlier month. ## When credits run out The Texa API answers **402** (`insufficient_credits`) to anything that would be counted. Top up in Texa Desk under **Texa AI → Credits and billing**, and requests work again at once. - **At zero**, Texa AI is paused for the account, so conversations on your channels go to your team on the customer's next message. A top-up resumes it by itself (a pause a person set stays). - **Warnings** go by email to your administrators at 20% and 5% of the credits last added, and at zero. - **Credits expire** 12 months after the account last added credits; a notice goes 30 days before, and any top-up keeps the whole balance. --- # Texa AI in your website and app Source: https://developers.texadesk.com/guides/website-and-app Put Texa AI in your own website and app. To a visitor who hasn't signed in it answers questions from your knowledge. Once they sign in, the same chat knows who they are and works in their account through your systems: it looks things up, makes changes with their yes, sends them files, takes them to the right page and fills in forms. Your team sees every chat in Texa Desk and takes over when it's needed. You never share your code with Texa. You give it four things, all set up in Texa Desk under Texa AI: 1. One script tag (or our app component) and a site key: visitors can chat at once. 2. One route on your server that says who is signed in. 3. What Texa AI may do in your systems: your API, your own server, MCP, or the Texa Relay. 4. The pages Texa AI may open, and any steps your page can do for the customer. ## 1. Visitors: one script tag In Texa Desk, **Developers → Website and app**, make a site key, and add your websites under **API keys → Websites your widget runs on** (live keys answer only there; the sandbox answers anywhere). Then add this to every page: ```html ``` A site key is safe in a page: it only opens visitor chats, from your websites, 20 an hour per address. A visitor is never treated as verified, and an action you keep for signed-in customers is refused for them: Texa AI asks them to sign in. ## 2. Signed-in customers: your server says who they are `data-session-url` is a route on **your** server. When someone is signed in, it makes a session with your secret key and returns it; when nobody is, it answers 401 and the widget stays a visitor's. Your key never reaches the page. ```js // Node (Express) with @texadesk/sdk app.post("/texa/session", requireSignIn, async (req, res) => { const s = await texa.widgetSession(`user-${req.user.id}`, { first_name: req.user.firstName, name: req.user.fullName, email: req.user.email, phone: req.user.phone, customer_token: req.user.apiToken, // optional: see step 3 }); res.json(s); }); ``` ```python # Python (FastAPI) with texa @app.post("/texa/session") def texa_session(user = Depends(signed_in_or_401)): return texa.widget_session(f"user-{user.id}", first_name=user.first_name, name=user.full_name, email=user.email) ``` ```php // PHP (Laravel) with texa/texa-php Route::post('/texa/session', fn () => auth()->check() ? $texa->widgetSession('user-' . auth()->id(), ['name' => auth()->user()->name]) : response()->json(['error' => 'not signed in'], 401)); ``` When a visitor signs in on a new page, the widget notices and the same chat carries on as them. In a single-page app, call `TexaWidget.signedIn()` after your sign-in, and `TexaWidget.signedOut()` after sign-out. **Signed in counts as verified.** With **Sign-in counts as verified** on (Developers → Website and app), Texa AI treats a customer your server signed in as verified, so they aren't asked for a one-time code. Leave it off if you want a code anyway (a bank moving money, say). ## 3. Acting as the customer Pass the customer's own token for your API as `customer_token` when you make their session. Then, in **Actions → Connectors → Your own system**, choose the sign-in **The signed-in customer's own token**: Texa sends it, in the header you name, on that customer's actions only. Your API decides what that person may see and change, exactly as for your own app. The token is sealed, never logged, never shown to Texa AI, and gone when the session ends. If it has ended, the action fails and Texa AI asks the customer to sign in again. If your own server runs actions instead (the `conversation.action_request` webhook), each request says who asks: `data.customer = {"ref": "user-1842", "signed_in": true}`. ## 4. What Texa AI may do Connect your system in **Actions**: - **Your API's OpenAPI file:** every operation becomes a draft action to review and save. - **No OpenAPI file?** Give the address of your API's documentation page: Texa AI drafts the actions from it, and you review and save each one. - **Your MCP server**, **your own server** (a signed webhook), or **the Texa Relay** inside your network. For each action, under **In your website or app chat**: - **Who can use it:** anyone, or signed-in customers only. - **Show the customer:** the file (a receipt, a statement: give the result that holds its link and its name), or a list as a spreadsheet. The customer gets it as a document in the chat. Texa AI uses an action only in a step of a procedure you approved, and asks the customer's yes before anything that changes something. ## 5. Pages and steps on your page **Pages Texa AI can open** (Developers → Website and app): name the pages customers ask for, such as *My documents* or *Payments*, or read them from your sitemap. When a customer asks where something is, Texa AI takes them there. By default the page loads; to use your own router: ```js window.TexaWidget.onOpenPage((url, page) => router.push(url)); ``` Texa AI opens a page only from a step of an approved procedure: on the same page, **Add the procedure** saves a draft of your procedures with the step "Show the customer a page", for you to approve under **Procedures**. **Screen actions** are steps your page does for the customer: filling in a form, opening a dialog. Add an action on the connector **Our website or app (screen actions)**, and handle it in your page: ```js window.TexaWidget.on("fill_delivery_address", async ({ street, city }) => { form.street.value = street; form.city.value = city; return { filled: true }; // the action's results, for Texa AI }); ``` The page has 30 seconds to answer; otherwise Texa AI is told it didn't, and says so. The script loads `async`: call these after the `texa:ready` event, or set them before it loads with `window.TexaWidgetSettings = { handlers: {...}, onOpenPage: fn }`. ## 6. In your app React Native and Expo, with `@texadesk/react-native`: ```tsx (user ? await api.post("/texa/session") : null)} signedIn={!!user} actions={{ fill_delivery_address: async (inputs) => ({ filled: true }) }} onOpenPage={(url, page) => navigation.navigate(page)} onPickFile={pickDocument} // your picker: { uri, name, type } /> ``` Flutter, with `texa_flutter` (coming soon: it isn't on pub.dev yet): ```dart TexaChat( siteKey: 'pk_live_...', getSession: () async => user == null ? null : WidgetSession.fromJson(await api.texaSession()), signedIn: user != null, actions: {'fill_delivery_address': (inputs) async => {'filled': true}}, onOpenPage: (url, page) => Navigator.pushNamed(context, url), ) ``` Both components are also clients without a screen (`TexaWidgetClient`), for your own design. The routes they call are in the [API reference](/api-reference/introduction), under widgets. ## 7. Files, documents and the typing preview - Customers send photos, PDFs, Word documents and recordings with the paper clip (10 MB at most). Texa AI reads them. - Documents Texa AI sends show as document messages; a spreadsheet downloads as a CSV. - While a chat is shown in Texa Desk, your team sees what the customer is typing, with a line telling the customer so. The widget masks it before it leaves the page: any run of six or more digits, and whatever follows *pin*, *otp*, *code*, *cvv* or *password*. Your own client must do the same before `POST .../typing`. ## 8. Your team With **Every chat in Texa Desk** on (the default), each chat shows in the **App conversations** inbox from its first message, waiting with Texa AI, with the customer's name, email, phone and city. When Texa AI hands over, or doesn't answer within 2 minutes, the same conversation opens for your team; their replies reach the customer in the chat. ## Before you go live - Your secret key stays on your server; only the session token and the site key reach a page. - Your websites are listed under **API keys**. - Each action that touches an account is kept for signed-in customers. - Try it all with a sandbox key and the sandbox site key first: nothing reaches a real customer. --- # A WhatsApp bot in 15 minutes Source: https://developers.texadesk.com/guides/whatsapp-bot **No code at all?** Connect your WhatsApp number in Texa Desk (Settings → Inboxes → Add inbox → WhatsApp) and switch Texa AI on for it under **Texa AI → Agent and inboxes**. This guide is for when your own server already talks to WhatsApp through Meta's Cloud API and you want Texa AI to answer. ## What you need - A WhatsApp Business number on Meta's Cloud API: its **phone number id** and an **access token**. Meta must send its webhook to your server. - A Texa test key (`texa_test_…`), from Texa AI → Developers. - Python 3.10+ with `fastapi`, `uvicorn` and `httpx`. ## The whole bot ```python # bot.py: run with uvicorn bot:app --port 8000 import os import httpx from fastapi import FastAPI, Request TEXA = httpx.Client(base_url="https://api.texadesk.com/v1", timeout=90, headers={"Authorization": f"Bearer {os.environ['TEXA_API_KEY']}"}) META = httpx.Client(base_url="https://graph.facebook.com/v21.0", timeout=30, headers={"Authorization": f"Bearer {os.environ['META_TOKEN']}"}) PHONE_ID = os.environ["META_PHONE_NUMBER_ID"] conversations: dict[str, str] = {} # customer's number -> Texa conversation (use your DB) app = FastAPI() def send_whatsapp(to: str, text: str) -> None: META.post(f"/{PHONE_ID}/messages", json={ "messaging_product": "whatsapp", "to": to, "type": "text", "text": {"body": text}}) def conversation_for(number: str, name: str | None) -> str: if number not in conversations: r = TEXA.post("/conversations", headers={"Idempotency-Key": f"wa-open-{number}"}, json={"channel": "whatsapp", "customer_ref": f"wa:{number}", "first_name": name}) conversations[number] = r.json()["conversation_id"] return conversations[number] @app.post("/whatsapp") # Meta's webhook (verify its signature too) async def incoming(request: Request): body = await request.json() for entry in body.get("entry", []): for change in entry.get("changes", []): value = change.get("value", {}) names = {c["wa_id"]: c["profile"]["name"] for c in value.get("contacts", [])} for m in value.get("messages", []): if m.get("type") != "text": continue number = m["from"] cid = conversation_for(number, names.get(number)) r = TEXA.post(f"/conversations/{cid}/messages", json={"message_id": m["id"], "text": m["text"]["body"]}) if r.status_code == 409 and r.json()["error"]["code"] == "conversation_closed": conversations.pop(number) # it ended: start a new one cid = conversation_for(number, names.get(number)) r = TEXA.post(f"/conversations/{cid}/messages", json={"message_id": m["id"], "text": m["text"]["body"]}) for event in r.json().get("events", []): if event["type"] == "reply": send_whatsapp(number, event["text"]) return {"ok": True} ``` Run it, point Meta's webhook at `https://your-server/whatsapp`, and message your number. ## Why it is safe to retry Meta sends a webhook again when your server is slow. Passing Meta's message id as `message_id` means Texa answers a repeat with the first answer (`"replayed": true`) and never replies twice. Opening with an `Idempotency-Key` means one number gets one conversation, however often the first message arrives. ## Things WhatsApp decides - **The 24-hour window.** You can send a free-form message only within 24 hours of the customer's last one. After that, Meta takes only an approved template. A reply that arrives later (see below) may need one. - **Replies that come later.** A voice note or photo is understood before Texa AI answers, so its reply can come after your request has returned. Subscribe a [webhook](/guides/webhooks) to `conversation.reply` and send those too. Use `reply_id` to make sure you send each reply once. - **People taking over.** When an event is a `handover`, Texa AI stops replying. With handover to Texa Desk, your team answers there, and each answer reaches you as the `conversation.human_message` webhook: send its `text` to the customer on WhatsApp. See [handing over](/guides/handover). - **Voice notes and photos.** Upload the file with `POST /v1/media` (with the `conversation_id`), then send a message whose `attachments` holds `{"media_id": ...}`. ## Going live Make a live key, swap it in, and nothing else changes. Everything is counted against your prepaid credits. Watch it in Texa Desk under **Texa AI → Credits and billing**. --- # Voice in your app Source: https://developers.texadesk.com/guides/voice Customers talk to Texa AI from your app or website, with the same agent, knowledge and procedures as on WhatsApp and the phone. Your **server** opens a voice conversation and asks for a single-use token. Your **app** opens the voice line with that token. The app never holds a key. ## 1. Your server: a voice conversation and a token ```python cid = texa.post("/conversations", json={ "channel": "voice", "customer_ref": "user-1842", "first_name": "Tolu"}).json()["conversation_id"] session = texa.post(f"/conversations/{cid}/voice-sessions", json={"ttl_s": 120}).json() # {"conversation_id": "cnv_...", "token": "...", "expires_at": "...", # "url": "wss://voice.texadesk.com/v1/voice?token=", "options": {}} ``` Give the app `session["token"]` and `session["url"]`. The token opens one session, once, before `expires_at` (`ttl_s` is 15 to 900 seconds). Anything about the session (text or voice mode, the voice) is set here in `options`, never by the app: | Option | Meaning | |---|---| | `mode` | `voice` (default) or `text`: the same line with typed turns | | `voice` | a voice from Texa Desk's Agent settings, e.g. `spitch:lina` | | `agent_name`, `agent_gender` | how the agent introduces itself | ## 2. Your app: the line Open `wss://voice.texadesk.com/v1/voice?token=&codec=&rate=`: - `codec`: `opus` (default; best on mobile networks) or `pcm` (16-bit little-endian mono). - `rate`: the sample rate, `8000` (default) or `16000`. The first message tells you exactly what the line carries: ```json {"type": "ready", "conversation_id": "cnv_...", "rate": 16000, "audio": {"codec": "pcm", "sample_rate": 16000, "sample_width": 2}, "persona": {"name": "Amaka", "language": "en", "gender": "female"}} ``` Then: | From Texa (text frames) | Do | |---|---| | binary frames | play them: Texa AI speaking | | `{"type": "utterance", "speaker": ..., "text": ...}` | a caption: what was said, by the customer or by Texa AI | | `{"type": "flush"}` | the customer interrupted: drop the audio you have queued | | `{"type": "ended", "end_reason": ..., "our_fault": false, "outcome": {...}}` | the call is over; `our_fault: true` means it ended on Texa's side | | `{"type": "error", "error": ...}` | the line could not open or failed; open a new session | | From your app | Means | |---|---| | binary frames | the microphone, in the codec and rate you asked for | | `{"type": "hangup"}` | the customer ended the call | | `{"type": "stats", "playout_underruns": n}` | optional: how often your playback ran dry, which helps Texa find a breaking voice | | `{"type": "say", "text": "..."}` | in `text` mode, one typed turn | If `ready` carries a `resume_token`, a line that drops (a tunnel, a lift) can rejoin within `rejoin_grace_s` seconds by opening `.../v1/voice?resume=`. Texa holds the call until then. ## A browser sketch (PCM at 16 kHz) ```js const ws = new WebSocket(`${url.replace('', token)}&codec=pcm&rate=16000`); ws.binaryType = 'arraybuffer'; const ctx = new AudioContext({ sampleRate: 16000 }); let playAt = 0; ws.onmessage = ({ data }) => { if (typeof data === 'string') { const msg = JSON.parse(data); if (msg.type === 'flush') playAt = ctx.currentTime; // drop what is queued if (msg.type === 'utterance') showCaption(msg.speaker, msg.text); if (msg.type === 'ended') stopMicrophone(); return; } const pcm = new Int16Array(data); const buf = ctx.createBuffer(1, pcm.length, 16000); buf.getChannelData(0).set(Float32Array.from(pcm, s => s / 32768)); const src = ctx.createBufferSource(); src.buffer = buf; src.connect(ctx.destination); playAt = Math.max(playAt, ctx.currentTime); src.start(playAt); playAt += buf.duration; }; // microphone -> 16-bit PCM frames of 20 ms (use an AudioWorklet in production) const mic = await navigator.mediaDevices.getUserMedia({ audio: { echoCancellation: true } }); const node = ctx.createScriptProcessor(320, 1, 1); node.onaudioprocess = e => { const f = e.inputBuffer.getChannelData(0); if (ws.readyState === 1) ws.send(Int16Array.from(f, s => Math.max(-1, Math.min(1, s)) * 32767).buffer); }; ctx.createMediaStreamSource(mic).connect(node); node.connect(ctx.destination); ``` Turn on echo cancellation, or Texa AI hears itself. Texa's own voice button and mobile components do all of this for you ([SDKs and widgets](/sdks)). ## After the call The conversation's transcript is `GET /v1/conversations/{id}/transcript`. Its events (`call.started`, `call.ended` with the minutes, `conversation.closed`) are on the feed and in webhooks. Voice minutes are counted against your prepaid credits. --- # Handing over to people Source: https://developers.texadesk.com/guides/handover Texa AI hands a conversation to people when a procedure says so, when the customer asks for a person, when actions fail twice, or when it cannot help. The answer (and the feed, and the `conversation.handover` webhook) then carries: ```json {"type": "handover", "handover_id": "hnd_...", "reason": "customer_asked", "priority": "high", "summary": "Sent N80,000 to the account ending 4471, meant 4417. Verified. Upset.", "facts": {"account_last4": "4471"}} ``` From then on **Texa AI does not reply**. The customer's messages are still recorded, so the conversation's record stays whole. Choose who answers, per environment, in Texa Desk under **Texa AI → Developers**: ## Your team, in Texa Desk (the default) The conversation appears in the **App conversations** inbox of your Texa Desk, with Texa AI's summary, what it established, the conversation so far, and the team, priority and labels your handover routes give (Texa AI → Agent and inboxes → Handover). Your team gets the usual Texa Desk notifications, on the web and in the mobile app. - Keep sending the customer's messages to `POST /v1/conversations/{id}/messages` as usual: they appear in the Desk conversation. - Each reply your team writes reaches your server as the **`conversation.human_message`** webhook: show `data.text` to the customer in your app. Subscribe to it, or replies from the Desk will not reach the customer. The Desk warns your team when there is no such webhook. ```json {"type": "conversation.human_message", "conversation_id": "cnv_...", "data": {"conversation_id": "cnv_...", "message_id": "cw_1022", "author": "Bola", "text": "I've found your transfer. It will be reversed within 24 hours.", "sent_at": 1790000123, "attachments": []}} ``` - When your team sets the conversation to **pending**, or assigns it back to Texa AI, Texa AI carries on. Its next replies come as `conversation.reply` webhooks. When they **resolve** it, the conversation closes. ## Your own system Your people answer in your own tools. Texa tells you with the `conversation.handover` webhook and does nothing more. Then: - `POST /v1/conversations/{id}/human-messages` with `{"text", "author"}` records what your person said, so Texa AI knows it when it takes over again. Show it to the customer yourself. - `POST /v1/conversations/{id}/handback` with a `note` of what was done gives the conversation back to Texa AI. Its answer carries Texa AI's next reply. - `POST /v1/conversations/{id}/close` closes it. ## Handing over yourself `POST /v1/conversations/{id}/handover` with a `reason` hands a conversation to people at any time, for example when your app's user taps "talk to a person". `POST /v1/conversations/{id}/assist` gives the person holding it a suggested reply. It is screened like every reply, and never sent by Texa. --- # The sandbox Source: https://developers.texadesk.com/guides/sandbox A test key (`texa_test_…`) reaches your sandbox: the same agent and knowledge, and the procedures your team approved, in a separate environment where nothing reaches a real customer and actions are answered by **fixtures** you set. Build everything here, then swap in a live key. ## Fixtures: what an action answers ```bash # answer get_transfer_status with this curl -X PUT https://api.texadesk.com/v1/sandbox/actions/get_transfer_status \ -H "Authorization: Bearer $TEXA_TEST_KEY" -H "Content-Type: application/json" \ -d '{"response": {"status": "succeeded", "data": {"status": "paid_out", "amount": 80000}}}' # a named scenario: open a conversation with {"scenario": "reversed"} to use it curl -X PUT https://api.texadesk.com/v1/sandbox/actions/get_transfer_status \ -H "Authorization: Bearer $TEXA_TEST_KEY" -H "Content-Type: application/json" \ -d '{"scenario": "reversed", "response": {"data": {"status": "reversed"}}}' # a fault, to see how Texa AI tells the customer curl -X PUT https://api.texadesk.com/v1/sandbox/actions/refund_payment \ -H "Authorization: Bearer $TEXA_TEST_KEY" -H "Content-Type: application/json" \ -d '{"response": {"status": "succeeded"}, "fault": {"kind": "timeout"}}' ``` A fixture's `response` is `{"status": "succeeded" | "failed", "data": {...}, "error": "..."}`. A `fault` imitates what real systems do: | `kind` | What happens | |---|---| | `slow` | answers after `ms` milliseconds | | `error` | answers failed, with `error` | | `timeout` | never answers: the action's own timeout fires | | `malformed` | answers with fields of the wrong type and fields nobody defined | | `injection` | answers with an instruction hidden in a text field, to show that Texa AI does not obey it | `GET /v1/sandbox/actions` lists your fixtures, and `DELETE /v1/sandbox/actions/{action}` (with `?scenario=`) removes one. These routes answer only a test key. An action with no fixture is not mocked: it reaches you, or your connector, exactly as it would live, so you can test your own code in the sandbox. An action connected in Texa Desk (a connector) runs against the sandbox's own connector settings, so you can point it at your staging system instead of a fixture. ## What is different from live | | Sandbox | Live | |---|---|---| | Customers | none: you play them | real | | Actions | fixtures, or your staging connectors | your systems | | Webhooks | sandbox endpoints; `http` allowed | live endpoints; `https` only | | Handing over | as set for the sandbox; Desk conversations carry the label `sandbox` | as set for live | | Rate limit | lower (see Texa Desk) | higher | | Usage | counted as `sandbox_requests` | counted per meter against your credits | Texa AI's replies in the sandbox come from the same models as live, so what you see is what your customers will get. --- # Customers and memory Source: https://developers.texadesk.com/guides/customers Texa AI remembers each customer across conversations: what they asked before, what was done, the facts it established. In the Texa API it knows them by **your** reference, `customer_ref`. ## Choose a reference Use an id from your own system that never changes for that person: `user-1842`, not an email address or a phone number they might change. Prefixes keep sources apart: `wa:2348031234567` for a WhatsApp number you haven't matched to an account yet. ## What Texa AI remembers ```bash curl https://api.texadesk.com/v1/customers/user-1842 -H "Authorization: Bearer $TEXA_API_KEY" ``` The answer is what Texa AI keeps about the customer. Use it to show your team what Texa AI knows, or to check what it will bring into the next conversation. You can give Texa AI facts at the start of a conversation with `known_facts` ("Gold customer since 2021; two cards, one blocked"): it uses them in that conversation without having to ask. ## Two references, one person When you learn that two references are the same person (a WhatsApp number that signs in to account `user-1842`), merge them so their history is one: ```bash curl -X POST https://api.texadesk.com/v1/customers/wa:2348031234567/merge \ -H "Authorization: Bearer $TEXA_API_KEY" -H "Content-Type: application/json" \ -d '{"into": "user-1842", "by": "sign-in"}' ``` `into` is the reference that stays. `by` says who or what merged them, for the record. ## Forget a customer When a customer asks you to delete their data (their right under the NDPA and similar laws), forget them: ```bash curl -X POST https://api.texadesk.com/v1/customers/user-1842/forget \ -H "Authorization: Bearer $TEXA_API_KEY" -H "Content-Type: application/json" \ -d '{"requested_by": "privacy@yourcompany.com, ticket 4471"}' ``` Texa AI's memory of the customer and their Texa API conversations are deleted. `requested_by` is kept in your audit log, so you can show the request was handled. A contact in Texa Desk is deleted there, through the Desk ([Delete contact](/api-reference/desk/contacts/delete-contact)). ## Texa Desk contacts Customers who reach you on a channel are **contacts** in Texa Desk, with their phone, email and custom attributes. Texa AI fills in what it learns (a name, a city) on the contact. Read and change contacts with the [Contacts API](/api-reference/desk/contacts/list-contacts), and hear about changes with the `contact_created` and `contact_updated` [webhooks](/guides/desk-webhooks). --- # Knowledge Source: https://developers.texadesk.com/guides/knowledge Texa AI answers only from knowledge your team approved: documents, web pages and help centre articles. Most companies add knowledge in Texa Desk (**Texa AI → Knowledge**), where they can also read a whole website (up to 10,000 pages from its sitemap: the first 30 at once, the rest in the background) or connect their help centre. The API is for keeping knowledge in step with a system of yours: a product catalogue, a price list, a policy library. ## Add a document ```bash curl -X POST https://api.texadesk.com/v1/knowledge/documents \ -H "Authorization: Bearer $TEXA_API_KEY" -H "Content-Type: application/json" \ -d '{"title": "Transfer limits", "kind": "policy", "body": "Daily transfer limits: Tier 1 accounts ₦50,000; Tier 2 ₦200,000; Tier 3 ₦5,000,000.", "uploaded_by": "policy-sync"}' ``` A document is **not used until a person approves it** in Texa Desk. This is deliberate: what Texa AI tells customers is your company's word, so a named person says yes to it. Approvers see the document under **Texa AI → Knowledge**, with who sent it (`uploaded_by`). ## See what's there ```bash curl https://api.texadesk.com/v1/knowledge/documents -H "Authorization: Bearer $TEXA_API_KEY" ``` Each document has its approval state, so a sync job can tell what is waiting for a person. ## Writing knowledge that works - **One topic per document**, with a title a customer would recognise. - **Facts, not marketing:** numbers, steps, conditions, exceptions. - **Say what's not possible**, too ("We don't deliver outside Lagos and Abuja"). Texa AI says it doesn't know rather than guess, so a missing fact becomes a handover. - **What changes often** (balances, order states) is not knowledge: it is an [action](/guides/actions). --- # Voice notes, photos and documents Source: https://developers.texadesk.com/guides/media Customers send more than text. Texa AI understands voice notes, photos, documents and videos: it transcribes, reads and describes them, then answers as if they had typed. ## Send a file Upload the file, then send a message that carries it: ```bash curl https://api.texadesk.com/v1/media \ -H "Authorization: Bearer $TEXA_API_KEY" \ -F conversation_id=cnv_8a7f0c... \ -F file=@voice-note.ogg ``` ```json {"media_id": "med_31c...", "kind": "audio", "status": "processing", "size": 48213, "sha256": "9f2c..."} ``` ```bash curl https://api.texadesk.com/v1/conversations/cnv_8a7f0c.../messages \ -H "Authorization: Bearer $TEXA_API_KEY" -H "Content-Type: application/json" \ -d '{"message_id": "wamid.HBg...", "attachments": [{"media_id": "med_31c..."}]}' ``` The turn waits for the file to be understood. A long recording can take longer than your request, so Texa AI's reply may arrive **later**, as a `conversation.reply` [webhook](/guides/webhooks) and on the event feed. Texa sends `media.understood` (or `media.failed`) when it's done. - A **location** goes as an attachment too: `{"type": "location", "latitude": 6.45, "longitude": 3.39}`. - `GET /v1/media/{media_id}` gives what was understood (the transcript, the description); `GET /v1/media/{media_id}/content` gives the bytes back. - Uploads are at most 64 MB through the API; the widget takes files of up to 10 MB. ## Replies as voice notes Customers who speak often want to hear the answer. Open the conversation with `"reply_audio": true` (or send it on a message to switch from then on), and each reply also carries `audio_media_id`: an OGG/Opus voice note of the reply, ready for WhatsApp. Fetch it from `/v1/media/{id}/content`. ## Counted Each file understood counts as a `media_items`, and audio and video by the second (`media_seconds`). See [usage and credits](/api-basics/usage). --- # Transcripts, quality and reports Source: https://developers.texadesk.com/guides/reports ## One conversation | Route | Gives | |---|---| | `GET /v1/conversations/{id}/transcript` | what was said, in order, by the customer, Texa AI and people | | `GET /v1/conversations/{id}/export` | everything: the transcript, every action with its inputs and result, handovers, and the procedure followed. For audits and disputes | | `GET /v1/conversations/{id}/quality` | the quality scores of a closed conversation | ```bash curl https://api.texadesk.com/v1/conversations/cnv_8a7f0c.../export -H "Authorization: Bearer $TEXA_API_KEY" ``` Fields Texa AI marks as private (an account number, a balance) are masked in what you read here, as they are in Texa Desk. ## Quality Every closed conversation is scored: whether it was resolved, whether Texa AI followed the procedure, whether its actions were right, and whether a handover was appropriate. A score arrives as the `quality.scored` webhook. The same scores drive **Texa AI → Quality** in Texa Desk. ## Reports ```bash curl "https://api.texadesk.com/v1/reports/support?since=2026-10-01&until=2026-10-19" \ -H "Authorization: Bearer $TEXA_API_KEY" ``` | Report | Carries | |---|---| | `GET /v1/reports/support` | `conversations`, `handover_rate`, `contact_reasons` (what customers wrote about, grouped into themes with quotes), `procedures` (conversations per procedure), `handovers`, `actions` with their `failure_rate`, and `knowledge_gaps`: what customers asked that your knowledge doesn't answer | | `GET /v1/reports/quality` | `conversations`, `resolved_rate`, `handover_rate`, `procedure_followed`, `actions_correct`, `handover_appropriate`, `first_reply_s`, `turns_to_resolution` | Both take `since` and `until` (ISO dates) and give back the `period`. Conversations in your own shadow mode (people answer, Texa AI drafts) are counted apart as `shadow_conversations`. For Texa Desk's own reports (your team's response times, conversations by inbox, agent and team), use the [Reports API](/api-reference/desk/reports/get-account-reports). For questions in plain words ("what are customers complaining about this week?"), connect your AI tools through [MCP](/guides/mcp). --- # Actions Source: https://developers.texadesk.com/guides/actions When Texa AI needs something from your systems, such as a transfer's status, a refund or a balance band, it asks with an **action request**. The request uses the action's name and typed inputs. What each action is (inputs, outputs, whether it changes something, whether the customer must say yes or be verified first) is set in Texa Desk under **Texa AI → Actions**. A named person approves it there. Texa AI never invents an action or runs one the procedures do not allow. There are three ways to run an action. You choose per action. | Way | Who holds your credentials | You write | |---|---|---| | **Texa calls your API**: a connector in Texa Desk (HTTP, OAuth 2, HMAC, a ready-made one such as Paystack or Flutterwave) | Texa, encrypted | nothing | | **Texa calls your server**: a signed webhook connector | you | one endpoint | | **You run it**: from the API's answer | you | a few lines around your API calls | If your systems speak MCP, a connector of type **MCP server** turns your tools into actions ([MCP](/guides/mcp)). The first two run by themselves. In the API's answer, the `action_request` event is marked `"handled_by": "texa"` with its result's status, and Texa AI's next reply follows it in the same `events`. Banks and fintechs that keep credentials inside use the second way, or the Texa Relay inside their network. ## You run it ```json {"type": "action_request", "request_id": "act_3f9c...", "action": "get_transfer_status", "inputs": {"reference": "TX-9B2LC"}, "idempotency_key": "idem_act_3f9c...", "expires_at": "2026-09-27T10:05:00Z"} ``` Run it, then give the result: ```python r = texa.post(f"/conversations/{cid}/action-results", json={ "request_id": event["request_id"], "status": "succeeded", # or "failed" "data": {"status": "paid_out", "amount": 80000}}) for e in r.json()["events"]: # Texa AI carries on: show its reply ... ``` - **Answer before `expires_at`.** Texa AI tells the customer it is checking, and past that time it moves on. - **A failure is a failure.** Send `"status": "failed"` with an `error` (at most 500 characters, for the log). Texa AI then says it did not work and offers a person. It never claims an action succeeded when it did not. - **Nothing found is not a failure.** When a lookup finds no record (no transfer with that reference), send `"status": "not_found"` with no data. Texa AI tells the customer no record matches and asks them to check it, instead of saying the check did not work. When Texa calls your API itself, a read's HTTP 404 means the same. - **Writes use `idempotency_key`.** If the same action request arrives twice (a retry, a timeout), your system must do it once. Pass `idempotency_key` to your payment or ledger API. - **Only return the fields the action declares.** Anything the action marks as private never reaches the customer. ## Texa calls your server (signed webhook) In Texa Desk, add a connector of type **Webhook** with your URL. You get a signing secret, shown once. For each request, Texa POSTs: ``` POST https://your-server/texa/actions X-Texa-Timestamp: 1790000000 X-Texa-Signature: sha256=" with your secret> X-Texa-Request-Id: act_3f9c... {"request_id": "act_3f9c...", "action": "get_transfer_status", "inputs": {"reference": "TX-9B2LC"}, "idempotency_key": "idem_act_3f9c...", "expires_at": "...", "conversation_id": "cnv_...", "environment": "live", "customer": {"ref": "cw:12:345", "name": "Ngozi Eze", "phone": "+2348031234567", "email": null}, "reply_url": "https://hooks.texadesk.com/actions/results/..."} ``` `customer` is the person as your Texa Desk contact knows them: the phone a WhatsApp message came from, the address an email came from. It says who is asking, not that they proved it. Texa AI checks identity before any action marked for verified customers only. An action you mark **Load at the start** (Actions screen) runs before Texa AI's first reply in every conversation, with `inputs` empty and this `customer`, so look the customer up by `phone` or `email`. `conversation_id` is `null` then. Check the signature and that the timestamp is within 5 minutes, then either: - answer **200** with `{"status": "succeeded", "data": {...}}`, `{"status": "not_found"}` or `{"status": "failed", "error": "..."}`, within the action's timeout, or - answer **202** and later POST the same body to `reply_url`, signed the same way with the same secret. ```python import hashlib, hmac, time def signed_by_texa(secret: str, timestamp: str, raw_body: bytes, signature: str) -> bool: if abs(time.time() - int(timestamp)) > 300: return False expected = "sha256=" + hmac.new(secret.encode(), timestamp.encode() + b"." + raw_body, hashlib.sha256).hexdigest() return hmac.compare_digest(expected, signature) ``` ## In the sandbox With a test key, set what an action answers, including faults, before you build it: ```bash curl -X PUT https://api.texadesk.com/v1/sandbox/actions/get_transfer_status \ -H "Authorization: Bearer $TEXA_TEST_KEY" -H "Content-Type: application/json" \ -d '{"response": {"status": "succeeded", "data": {"status": "paid_out", "amount": 80000}}}' ``` See [the sandbox](/guides/sandbox) for scenarios and faults. --- # Ready-made connectors Source: https://developers.texadesk.com/guides/connectors A ready-made connector gives Texa AI actions in a system you already use. You paste your keys, review the actions, and name them in your procedures. Set them up in Texa Desk under **Texa AI → Actions → Connectors**, separately for the sandbox (test keys) and live. | Connector | Category | Actions | |---|---|---| | **Paystack** | payments | `get_payment_status` · `get_transfer_status` (verified customers) · `refund_payment` (changes something; verified, with the customer's yes) | | **Flutterwave** (v4) | payments | `get_payment_status` · `get_transfer_status` (verified) · `refund_payment` (verified, with the customer's yes) | | **Monnify** (Moniepoint) | payments | `get_payment_status` | | **M-Pesa** (Daraja) | payments | `get_mpesa_payment_status`: whether an M-Pesa Express request was paid | | **Shopify** | commerce | `get_order` (verified) · `cancel_order` for an unshipped order (verified, with the customer's yes) | | **WooCommerce** | commerce | `get_order` (verified) | | **HubSpot** | CRM | `find_customer` by phone or email · `open_ticket` | | **Salesforce** | CRM | `find_customer` by phone · `open_case` | | **Zoho CRM** | CRM | `find_customer` by phone · `add_crm_note` | "Verified" means Texa AI checks who the customer is before it runs the action (a one-time code to the phone or email on record, or your own sign-in). "With the customer's yes" means it states exactly what it will do and waits for them to agree. ## What you control For each action you can change, before you save it: - **Private outputs.** An amount or a recipient's account marked private is never said back to an unverified customer. - **Who may use it** in your website or app chat: anyone, or signed-in customers only. - **Limits** on actions that move money, such as the largest refund. - **Load at the start:** a lookup that runs before Texa AI's first reply, so it already knows the customer's record. Every call is recorded under the action (**the last 50 calls**), with account and card numbers, PINs, keys, emails, phone numbers and balances hidden. Test an action from the same screen: reads run against live, writes only in the sandbox. ## Not listed? Connect [your own API](/guides/your-api) (an OpenAPI file, or a documentation page Texa AI reads), [your own server](/guides/action-server), or an [MCP server](/guides/mcp). Ask for a connector at [info@texadesk.com](mailto:info@texadesk.com). --- # Connect your own API Source: https://developers.texadesk.com/guides/your-api If your system has an HTTP API, Texa can call it for Texa AI with no code on your side. Your credentials are stored encrypted and never shown again; Texa AI never sees them. ## 1. Add the connector In Texa Desk, **Texa AI → Actions → Connectors → Your own system**, choose **Texa calls your API** and give its base address (`https://api.yourcompany.com`). Then pick how Texa signs in: | Sign-in | Sends | |---|---| | API key | a header (`X-Api-Key: …`) or a query value you name | | Bearer token | `Authorization: Bearer …` | | Basic | `Authorization: Basic …` from a username and password | | OAuth 2 client credentials | gets a token from your token URL, keeps it until it expires and refreshes it before | | HMAC signing | a signature header over the body (or `timestamp.body`), SHA-256, hex or base64 | | The signed-in customer's own token | the customer's token from your website or app, in the header you name, on that customer's actions only ([how](/guides/website-and-app#3-acting-as-the-customer)) | ## 2. Make the actions - **Import an OpenAPI 3 file:** every operation becomes a draft action. - **No OpenAPI file?** Give the address of your API's documentation page. Texa AI drafts the actions from it. - **Or write one by hand.** Nothing is saved until you review each draft: its description, inputs, outputs, which outputs are private, whether it changes something, and whether the customer must be verified first. ## 3. The request and the answer Under the hood, an action is a request template and a mapping of the answer. You can edit both: ```yaml request: method: GET path: "/transfers/{{ inputs.reference | urlencode }}" response: success_when: {path: "$.status", equals: true} error: {path: "$.message"} outputs: status: {path: "$.data.status", map: {success: paid_out, pending: processing, "*": failed}} amount: {path: "$.data.amount", divide: 100} sent_at: {path: "$.data.created_at", date: true} ``` - **Placeholders:** `{{ inputs.name }}`, `{{ secrets.api_key }}`, `{{ idempotency_key }}`, with filters `urlencode`, `kobo` (naira to kobo), `upper`, `lower`, `digits`, `int`, `str`, `json`, `hexkey` (a 40-character key for APIs that take letters and digits only). In a JSON body, a value that is only a placeholder keeps its type. A missing value is an error that names it, never an empty value sent by accident. - **Paths** are a small, safe part of JSONPath: `$`, `.key`, `[n]`, `[*]`. No expressions, no scripts. - **A read's HTTP 404** means "no such record" (Texa AI asks the customer to check), not a failure. - **Writes carry `idempotency_key`:** pass it to your API so a retry doesn't do it twice. ## Where Texa will and won't call - Only the hosts the connector is set up for: its base address, plus any you list. - `https` for live connectors. - **Never a private address** (`10.x`, `192.168.x`, `localhost`, cloud metadata), however the name resolves, checked again at connect time. Redirects are not followed. To reach systems inside your network, use [your own server](/guides/action-server) or the [Texa Relay](/guides/relay). --- # Run actions on your server Source: https://developers.texadesk.com/guides/action-server When you'd rather keep your credentials and logic on your side, Texa sends each action request to your server and you answer with the result. There are two ways to receive them. | | Signed webhook | Socket | |---|---|---| | Texa connects to | your `https` URL | nothing: your server connects to Texa | | Needs an inbound port | yes | no | | Good for | a public API server | a server behind a firewall, a laptop while you build | Both are set up in Texa Desk under **Texa AI → Actions → Connectors → Your own system**. ## Signed webhook Texa POSTs each request, signed with the connector's secret: ```http POST https://your-server/texa/actions X-Texa-Timestamp: 1790000000 X-Texa-Signature: sha256="> X-Texa-Request-Id: act_3f9c... {"request_id": "act_3f9c...", "action": "get_transfer_status", "inputs": {"reference": "TX-9B2LC"}, "idempotency_key": "idem_act_3f9c...", "expires_at": "2026-10-19T10:05:00Z", "conversation_id": "cnv_...", "environment": "live", "customer": {"ref": "cw:12:345", "name": "Ngozi Eze", "phone": "+2348031234567", "email": null}, "reply_url": "https://hooks.texadesk.com/actions/results/..."} ``` [Check the signature](/authentication#checking-what-texa-sends-you), then either: - answer **200** with `{"status": "succeeded", "data": {...}}`, `{"status": "not_found"}` or `{"status": "failed", "error": "..."}` within the action's timeout, or - answer **202**, and later POST the same body to `reply_url`, signed the same way. `customer` says who is asking as Texa Desk knows them (the phone a WhatsApp message came from), not that they proved it. Texa AI checks identity first for actions marked for verified customers only. A request from your website or app chat also says `"signed_in": true` when your server signed the customer in. ## Socket Your server opens one WebSocket to Texa and keeps it open; requests come down it. ```text wss://hooks.texadesk.com/actions/connect Authorization: Bearer ``` | Direction | Message | |---|---| | Texa → you | `{"type": "ready"}` once connected | | Texa → you | `{"type": "action_request", "reply_token", "request_id", "action", "inputs", "idempotency_key", "expires_at", "conversation_id"}` | | you → Texa | `{"type": "action_result", "reply_token", "status", "data", "error"}` | | you → Texa | `{"type": "ping"}`, answered `{"type": "pong"}` | ```python # pip install websockets import asyncio, json, os, websockets async def main(): async with websockets.connect("wss://hooks.texadesk.com/actions/connect", additional_headers={"Authorization": f"Bearer {os.environ['TEXA_SOCKET_KEY']}"}) as ws: async for raw in ws: msg = json.loads(raw) if msg["type"] != "action_request": continue data = await run(msg["action"], msg["inputs"], msg["idempotency_key"]) await ws.send(json.dumps({"type": "action_result", "reply_token": msg["reply_token"], "status": "succeeded", "data": data})) asyncio.run(main()) ``` Reconnect when the socket drops (with a pause that grows). A request with no socket connected, or no answer before `expires_at`, fails, and Texa AI tells the customer honestly that it couldn't check. ## Or run it from the API's answer If your own server is already the one calling the Texa API, you don't need either: action requests come back in the answer's `events`, and you answer with `POST /v1/conversations/{id}/action-results`. See [actions](/guides/actions#you-run-it). --- # MCP Source: https://developers.texadesk.com/guides/mcp Texa works with the Model Context Protocol in both directions. ## Your AI tools ask Texa Texa is an MCP server. An assistant your team already uses can ask about your customers, and every answer is backed by real conversations. It connects over Streamable HTTP: - **URL:** `https://api.texadesk.com/mcp` - **Header:** `Authorization: Bearer texa_live_…` | Tool | What it does | |---|---| | `ask_customers` | Answers a question such as "what are customers complaining about this week?" from your conversations, citing the conversation and the exact words for every claim. When the conversations do not say, it answers that it cannot answer from the data. Needs a live key | | `list_conversations` | Recent conversations, by state or customer | | `get_transcript` | What was said in one conversation | | `support_report` | The support report for the last few days | | `usage` | What has been counted this month | Every tool only reads. Tool calls count as requests, and each question as an Ask AI question, against your credits. They appear in your request log. ## Your MCP server runs Texa's actions If your systems already speak MCP, add a connector of type **MCP server** in Texa Desk (**Texa AI → Actions → Connectors**), with your server's `https` address and, if it needs one, a bearer token. **Import tools** lists your tools as draft actions. Review each one (its outputs, which fields are private, whether the customer must be verified, and for tools that change something, that the customer must say yes), then save it. Texa AI then asks for those actions like any other: gated, recorded, and told honestly when they fail. A tool's `structuredContent`, or its JSON text, is the action's result, and `isError` is a failure. --- # The Texa Relay Source: https://developers.texadesk.com/guides/relay !!! note "On request" The Texa Relay is built and tested. Its connection point on Texa's hosting is set up per company: ask [info@texadesk.com](mailto:info@texadesk.com) before you plan a rollout. The Texa Relay lets Texa AI look things up in your systems (a transfer's status, an account's state) **without your data or credentials leaving your network** and **without opening any inbound port**. It is a single program (also a container image) that runs on a server of yours, connects *out* to Texa over mutual TLS, and runs only the operations your administrator wrote, reviewed and signed. ## What it can and cannot do - It runs **only the named operations in your signed file**: a `SELECT` your team wrote, or a call to one of your internal services. Texa sends an operation's name and values for its declared parameters, **never SQL, a URL or a header**. - **Queries are read-only.** Each is checked to be a single `SELECT`, the database login must be read-only (checked at start), and every query runs in a read-only transaction. - **Limits you set:** rows, time, response size, requests a minute, how many at once. - **Masking:** account numbers to the last four digits, card numbers to the first six and last four, BVN and NIN hashed, dates of birth to the year. Per column, or by column name everywhere. - **Customer checks:** an operation can require that Texa has verified the customer first. - **An audit log on your disk**, hash-chained, so a changed or removed line shows. - **Your kill switch:** create `/var/lib/texa-relay/KILL` (or send `SIGUSR1`) and nothing runs until it's removed. Texa can't lift your switch. ## Databases and systems Postgres, MySQL, SQL Server and Oracle, and HTTP services answering JSON, XML, OFS or text. Templates are included for **Finacle**, **Temenos Transact (T24)** and **Oracle FLEXCUBE**. ## Setting it up The full guide for your IT team, with the configuration file, signing, enrolment and operations, is in [Hosting and security → Install the Texa Relay](/hosting/relay-install). In outline: 1. Your administrator makes a signing key and keeps the private half off the Relay server. 2. Your team writes `relay.yaml`: the databases, and each operation's query, parameters, outputs and masking. Start from a template. 3. The administrator signs the file. The Relay refuses an unsigned or changed file. 4. In Texa Desk, **Texa AI → Actions → Relay → Add a relay**, then run the enrol command it shows on your server. The token works once, for 24 hours. 5. **Bind** each operation to an action, and name the action in your procedures. You see each relay's health, operations and alerts on the same screen, and can switch one off. --- # Texa API webhooks Source: https://developers.texadesk.com/guides/webhooks Texa POSTs each event to your server as it happens. Add an endpoint in Texa Desk under **Texa AI → Developers → Webhooks**: its URL, live or sandbox events, and every event type or the ones you pick. You get a **signing secret**, shown once. - Live endpoints must be `https` and reachable from the internet. Sandbox endpoints may use `http` while you build (through a tunnel such as ngrok: private addresses are never called). - **Send a test event** in Texa Desk posts a `ping` at once and shows the answer. ## What arrives ``` POST https://your-server/texa/webhooks Content-Type: application/json X-Texa-Timestamp: 1790000000 X-Texa-Signature: sha256=5d41402abc4b2a76b9719d911017c592... X-Texa-Event-Id: evt_431 X-Texa-Event-Type: conversation.reply X-Texa-Delivery-Id: whd_12 {"id": "evt_431", "type": "conversation.reply", "created_at": "2026-09-27T10:00:03Z", "environment": "live", "conversation_id": "cnv_8a7f0c...", "data": {"type": "reply", "reply_id": "rpl_...", "format": "chat", "text": "..."}} ``` The body is the same event as on `GET /v1/events`, plus `environment`. ## Check it came from Texa Compute the signature over the **raw** body, before any JSON parsing, and compare in constant time. Reject a timestamp more than 5 minutes old. ```python import hashlib, hmac, time def verify(secret: str, headers, raw_body: bytes) -> bool: ts = headers["X-Texa-Timestamp"] if abs(time.time() - int(ts)) > 300: return False expected = "sha256=" + hmac.new(secret.encode(), ts.encode() + b"." + raw_body, hashlib.sha256).hexdigest() return hmac.compare_digest(expected, headers["X-Texa-Signature"]) ``` ```js import crypto from 'node:crypto'; export function verify(secret, headers, rawBody) { const ts = headers['x-texa-timestamp']; if (Math.abs(Date.now() / 1000 - Number(ts)) > 300) return false; const expected = 'sha256=' + crypto.createHmac('sha256', secret) .update(`${ts}.`).update(rawBody).digest('hex'); const given = headers['x-texa-signature'] || ''; return given.length === expected.length && crypto.timingSafeEqual(Buffer.from(given), Buffer.from(expected)); } ``` The same signature scheme is used on every request Texa sends you: action webhooks and monitor alerts too. ## Answer fast, handle once - Answer any **2xx within 10 seconds**, then do the work. Anything else, or no answer, is tried again after 1 minute, 5 minutes, 30 minutes, then 2, 5, 10 and 10 hours: 8 attempts over about a day. After that the delivery is marked failed, and you can send it again from Texa Desk. - Delivery is **at least once**: use `X-Texa-Event-Id` (or the body's `id`) to ignore a repeat. - Order is not guaranteed across retries. Use `created_at`, or the feed, when order matters. - An endpoint whose every delivery has failed for **5 days** is switched off, and Texa Desk says so. Switch it on again once it is fixed. - Each delivery's attempts (time, status, error) are in Texa Desk, for 30 days. The feed, `GET /v1/events?after=`, is the complete record. If your server was down, read the feed from your last cursor instead of waiting for retries. ## Event types | Type | When | |---|---| | `conversation.opened` | a conversation was opened | | `conversation.reply` | Texa AI replied, including replies that came after your request returned | | `conversation.action_request` | Texa AI asked for an action (codes the customer typed are masked here) | | `conversation.handover` | Texa AI handed the conversation to people | | `conversation.handback` | people handed it back to Texa AI | | `conversation.closed` | it was closed; `resolved` says whether Texa AI resolved it | | `conversation.human_message` | a person answered from Texa Desk: show `text` in your app ([handing over](/guides/handover)) | | `call.started`, `call.ended` | a phone or in-app call started or ended | | `media.understood`, `media.failed` | a voice note, photo, document or video was understood, or could not be | | `quality.scored` | a closed conversation was scored | | `monitor.finding` | one of your monitors found something | | `interview.completed` | a research interview was completed | | `report.ready` | a report is ready | | `survey.response` | a customer completed a survey | | `ping` | a test event you sent from Texa Desk | New types may be added. Ignore types you do not know, and subscribe to types by name if you want only those. --- # Texa Desk webhooks Source: https://developers.texadesk.com/guides/desk-webhooks Texa Desk webhooks tell your server what happens in the helpdesk: new conversations, every message (from customers, your team and Texa AI), contact changes and tickets. Use them to copy conversations into your own systems, update a CRM, or trigger your own work. For events from **Texa API** conversations (replies, action requests, handovers), use the [Texa API webhooks](/guides/webhooks) instead. ## Add a webhook In Texa Desk, **Settings → Integrations → Webhooks → Add new webhook**: your URL and the events you want. Each webhook has a **secret** (shown with an eye button, and copyable) that signs every delivery. Or use the API: [Add a webhook](/api-reference/desk/webhooks/add-a-webhook). ## Events | Event | When | |---|---| | `conversation_created` | a conversation starts | | `conversation_updated` | a conversation's attributes change (assignee, team, labels, custom attributes, its ticket) | | `conversation_status_changed` | it becomes open, pending, snoozed or resolved | | `message_created` | a message is added: incoming, outgoing, or a private note | | `message_updated` | a message changes (delivered, read, failed, edited) | | `contact_created`, `contact_updated` | a contact is added or changed (including a merge) | | `conversation_typing_on`, `conversation_typing_off` | someone in your team starts or stops typing | | `webwidget_triggered` | a visitor opens the live chat on your website | | `inbox_created`, `inbox_updated` | an inbox is added or changed | | `ticket_created`, `ticket_updated`, `ticket_merged` | a [ticket](/desk/tickets) is made, changed (state, type, person) or merged | ## What arrives ```http POST https://your-server/texa-desk/webhooks Content-Type: application/json X-Chatwoot-Delivery: 7b0d0d6e-0f6c-4b1e-9c0e-2b0d6a3a9f11 X-Chatwoot-Timestamp: 1790000000 X-Chatwoot-Signature: sha256=" with the webhook's secret> ``` ```json { "event": "message_created", "id": 50211, "content": "Hello, my transfer of 80,000 has not arrived", "message_type": "incoming", "content_type": "text", "private": false, "created_at": "2026-10-19T10:00:03.112Z", "source_id": "wamid.HBgN...", "sender": {"id": 345, "name": "Ngozi Eze", "phone_number": "+2348031234567", "type": "contact"}, "conversation": {"id": 171, "inbox_id": 12, "status": "pending", "...": "..."}, "inbox": {"id": 12, "name": "WhatsApp"}, "account": {"id": 1, "name": "Pilot Bank"} } ``` - **Messages** carry `content`, `message_type` (`incoming`, `outgoing`, `activity`, `template`), `private` (a note your team only), `sender`, `conversation`, `inbox`, `account`, and `attachments` when there are files. - **Conversation events** carry the conversation (with its `meta.sender`, `meta.assignee`, labels, custom attributes and `ticket`) and, for updates, `changed_attributes`: each changed field as `[before, after]`. - **Contact events** carry the contact: `id`, `name`, `email`, `phone_number`, `identifier`, `custom_attributes`, `additional_attributes`. - **Ticket events** carry the ticket (reference, title, type, state, person, team, promise), `contact`, `conversations`, and for `ticket_updated` its `changed_attributes`, the state and type by name: `"state": ["New", "Waiting on customer"]`. ## Check the signature The signature is HMAC-SHA256 over `"."` with the webhook's secret, written as `sha256=`. Compute it over the **raw** body, before parsing, compare in constant time, and reject a timestamp older than five minutes. The code is the same as for [Texa's own signatures](/authentication#checking-what-texa-sends-you), with these header names. These header names come from the open-source Chatwoot helpdesk Texa Desk is built on, and are kept so that existing integrations work unchanged. ## Answer fast - Texa Desk waits **5 seconds** for your answer and sends each event **once**: there are no retries. Answer `200` at once and do the work afterwards (put the event on your own queue). - Use `X-Chatwoot-Delivery` to spot a repeat. - If your server was down, catch up from the API: [list conversations](/api-reference/desk/conversations/conversations-list) updated since then, and [their messages](/api-reference/desk/messages/get-messages). ## Per inbox: API inboxes An [API inbox](/desk/api-channel) has its own webhook URL, which gets that inbox's events, signed with the inbox's secret. That's how your own channel hears your team's replies. --- # Live chat on your website Source: https://developers.texadesk.com/desk/live-chat Live chat puts a chat bubble on your website. Every chat lands in Texa Desk, where your team (and Texa AI, if you turn it on) answers it. This page covers adding it to any website, telling it who the visitor is, and checking that nobody pretends to be someone else. ## Add it to your website {#install} In Texa Desk, open **Settings → Inboxes → Add inbox → Website** and give it your website's name and address. The last step shows your code. You can copy it again at any time from the inbox's **Configuration** tab. It looks like this: ```html ``` Paste it on every page, just before ``. Most website builders have a place for it: | Where your site is built | Where the code goes | |---|---| | WordPress | Use our plugin (below), or paste the code with the free plugin **WPCode** (**Code snippets → Header & footer → Footer**). | | Wix | **Settings → Custom code → Add custom code**: body end, all pages. Needs a paid Wix plan with a connected domain. | | Shopify | **Online store → Themes → ⋯ → Edit code → `theme.liquid`**, just before ``. | | Squarespace | **Settings → Advanced → Code injection → Footer**. | | Webflow | **Site settings → Custom code → Footer code**, then publish. | | Google Tag Manager | A new **Custom HTML** tag, triggered on **All pages**. | | Your own code (React, Next.js, Vue…) | Load it once in your main layout, the same way as any other script. | ### WordPress plugin {#wordpress} 1. [Download the plugin](https://api.texadesk.com/docs/downloads/texa-desk-chat.zip). 2. In WordPress, go to **Plugins → Add New → Upload Plugin**, choose the file and activate it. 3. Open **Settings → Texa Desk chat** and paste your website token: the value after `websiteToken:` in the code above. Save. 4. Optional: paste the **Secret key** from the inbox's Configuration tab too. People signed in to your WordPress site then chat as themselves, with identity validation, and the chat forgets them when they sign out. Open your website and the chat bubble appears in the corner. Send a test message and it shows up in Texa Desk under that inbox. ## Settings Set `window.texaSettings` before the code runs to change how the chat looks: ```html ``` ## Tell it who the visitor is When a visitor is signed in on your website, pass their details so your team sees who they are and their past chats follow them across devices: ```js window.addEventListener("texa:ready", function () { window.$texa.setUser("user-1042", { // your own id for this person name: "Ada Obi", email: "ada@example.com", phone_number: "+2348012345678", identifier_hash: "..." // see Identity validation below }); }); ``` Call `window.$texa.reset()` when they sign out, so the next person on that device starts a new chat. Other commands on `window.$texa`: | Command | What it does | |---|---| | `setCustomAttributes({ plan: "Gold" })` | Saves facts about the person on their contact | | `deleteCustomAttribute("plan")` | Removes one | | `setConversationCustomAttributes({ order: "A-77" })` | Saves facts on the current chat | | `setLabel("vip")` / `removeLabel("vip")` | Adds or removes a label on the chat | | `setLocale("fr")` | Changes the chat's language | | `setColorScheme("dark")` | `"light"`, `"dark"` or `"auto"` | | `toggle("open")` / `toggle("close")` | Opens or closes the chat | | `toggleBubbleVisibility("hide")` | Hides or shows the bubble (`"show"`) | | `popoutChatWindow()` | Opens the chat in its own window | | `reset()` | Forgets the visitor (on sign-out) | ## Identity validation {#identity-validation} Without it, anyone who knows how the code works could call `setUser` with someone else's id and read their chats. Identity validation stops that: your server signs the visitor's id with a secret key, and Texa accepts the id only with the right signature. 1. In Texa Desk, open the inbox's **Configuration** tab and copy the **Secret key** under Identity validation. Keep it on your server. Never put it in a web page or an app. 2. On your server, make `identifier_hash`: an HMAC-SHA256 of the visitor's id with the secret key, written as hex. 3. Send the hash to the page and pass it to `setUser` with the same id. ```js // Node.js const crypto = require("crypto"); const identifierHash = crypto .createHmac("sha256", process.env.TEXA_CHAT_SECRET) .update(String(user.id)) .digest("hex"); ``` ```python # Python import hashlib, hmac, os identifier_hash = hmac.new(os.environ["TEXA_CHAT_SECRET"].encode(), str(user.id).encode(), hashlib.sha256).hexdigest() ``` ```php // PHP (WordPress too: get_current_user_id()) $identifier_hash = hash_hmac('sha256', (string) $user_id, getenv('TEXA_CHAT_SECRET')); ``` ```ruby # Ruby identifier_hash = OpenSSL::HMAC.hexdigest("sha256", ENV["TEXA_CHAT_SECRET"], user.id.to_s) ``` Turn on **Require identity validation for all conversations** once your website sends the hash for every signed-in visitor. From then on a `setUser` without a valid hash is refused. If the secret key is ever exposed, press **Rotate key** and put the new key on your server. Hashes made with the old key stop working at once. ## Events The chat tells your page what happens, so you can react to it: | Event | When | |---|---| | `texa:ready` | The chat has loaded and `window.$texa` is ready | | `texa:on-message` | A message arrives in the chat (`event.detail` has it) | | `texa:on-start-conversation` | The visitor starts a new chat | | `texa:opened` / `texa:closed` | The chat window opens or closes | | `texa:error` | Something went wrong, such as a refused identity | ```js window.addEventListener("texa:on-message", function (event) { console.log("New message", event.detail); }); ``` ## In a mobile app To show this chat inside an iOS or Android app, turn on **Enable widget in mobile apps** on the inbox's **Configuration** tab. Apps don't send a website address, so without it the chat is refused when you limit it to your own domains. For Texa AI that works inside your app and in your customers' accounts, see [Your website and app](/guides/website-and-app). ## Older code Code that calls `window.chatwootSDK`, `window.$chatwoot`, `window.chatwootSettings` or listens for `chatwoot:` events keeps working. New code should use the `texa` names above. --- # WordPress plugin Source: https://developers.texadesk.com/desk/wordpress 1. [Download the plugin](https://api.texadesk.com/docs/downloads/texa-desk-chat.zip) (`texa-desk-chat.zip`). 2. In WordPress, **Plugins → Add New → Upload Plugin**, choose the file, then **Activate**. 3. Open **Settings → Texa Desk chat** and paste your **website token**: in Texa Desk, open the website inbox's **Configuration** tab; it's the value after `websiteToken:` in the code there. Save. 4. Optional: paste the inbox's **Secret key** too (Configuration → Identity validation). People signed in to your WordPress site then chat as themselves, with [identity validation](/desk/live-chat#identity-validation), and the chat forgets them when they sign out. Open your site and the chat bubble appears. Send a test message: it arrives in Texa Desk in that inbox. ## Without the plugin Paste the inbox's code in your theme's footer with a snippet plugin such as **WPCode** (Code snippets → Header & footer → Footer). See [live chat](/desk/live-chat) for the code and its settings. --- # Bring your own channel Source: https://developers.texadesk.com/desk/api-channel An **API inbox** is an inbox whose messages come from your code. Customers' messages go in through the API; your team's (and Texa AI's) replies come out to your webhook. Everything else (assignment, labels, tickets, reports, Texa AI) works as in any inbox. ## 1. Make the inbox In Texa Desk, **Settings → Inboxes → Add inbox → API**. Give it a name and, optionally, a **webhook URL**: where Texa Desk sends this inbox's events, so your channel can deliver replies. The inbox's **Configuration** tab shows its **inbox identifier**, its **HMAC token** (for identity validation) and the webhook secret. ## 2. Send customers' messages in There are two ways, depending on who holds the credentials. ### From your server: the Application API Your server, with an access token, creates the contact and conversation and posts each message as `incoming`: ```bash # a conversation for a contact in the API inbox curl -X POST https://desk.texadesk.com/api/v1/accounts/1/conversations \ -H "api_access_token: $TEXA_DESK_TOKEN" -H "Content-Type: application/json" \ -d '{"inbox_id": 14, "contact_id": 345, "source_id": "kiosk-user-88"}' # the customer's message curl -X POST https://desk.texadesk.com/api/v1/accounts/1/conversations/171/messages \ -H "api_access_token: $TEXA_DESK_TOKEN" -H "Content-Type: application/json" \ -d '{"content": "Is my card ready for collection?", "message_type": "incoming"}' ``` ### From the customer's device: the Client API The [Client API](/api-reference/client) needs no secret, so an app can call it directly. It knows the inbox by its identifier and the customer by a contact identifier. ```bash # 1. a contact (keep the source_id: it is this customer's identifier from now on) curl -X POST https://desk.texadesk.com/public/api/v1/inboxes/$INBOX_IDENTIFIER/contacts \ -H "Content-Type: application/json" \ -d '{"identifier": "user-1842", "name": "Tolu Bello", "email": "tolu@example.com", "identifier_hash": ""}' # 2. a conversation curl -X POST https://desk.texadesk.com/public/api/v1/inboxes/$INBOX_IDENTIFIER/contacts/$CONTACT_ID/conversations # 3. a message curl -X POST https://desk.texadesk.com/public/api/v1/inboxes/$INBOX_IDENTIFIER/contacts/$CONTACT_ID/conversations/1/messages \ -H "Content-Type: application/json" -d '{"content": "Is my card ready for collection?"}' ``` The contact identifier is the customer's key to their conversations: keep it only on their device or behind their sign-in. With identity validation on (the inbox's settings), a contact for a known `identifier` is created only with a valid `identifier_hash`, which only your server can make. ## 3. Deliver replies out Every reply your team or Texa AI writes arrives at the inbox's webhook URL as `message_created` with `message_type: "outgoing"`, signed with the inbox's secret ([how to check](/guides/desk-webhooks#check-the-signature)). Deliver it on your channel. Skip `private: true` messages: they're notes for your team. Then update its status, so your team sees whether it reached the customer: [Update message status](/api-reference/desk/messages/update-message-status) (`delivered`, `read`, `failed`). ## Texa AI on your channel Switch Texa AI on for the inbox like any other (**Texa AI → Agent and inboxes**). It answers in the inbox, and its replies reach your webhook the same way. If you only want Texa AI and no helpdesk around it, the [Texa API](/quickstart) is simpler. --- # Agent bots Source: https://developers.texadesk.com/desk/agent-bots An **agent bot** is a bot you host. Texa Desk sends it the events of the inboxes it's connected to; it answers through the Texa Desk API with its own token, and hands the conversation to people by opening it. This is how anyone can put their own automation in front of a team. !!! tip "Texa AI already does this" Texa AI is Texa Desk's own agent, with procedures, actions and handover built in. Build an agent bot when you need logic of your own: a menu for a kiosk, a partner's bot, a rules engine. ## 1. Create the bot In Texa Desk, **Settings → Bots → Add bot**: a name, an avatar and the **webhook URL** where it receives events. The bot gets an **access token**. Or create it with the API: [Create an agent bot](/api-reference/desk/account-agent-bots/create-an-agent-bot). Then connect it to an inbox: the inbox's settings → **Bot configuration**, or [Add or remove agent bot](/api-reference/desk/inboxes/add-or-remove-agent-bot). ## 2. What the bot receives | Event | When | |---|---| | `message_created` | a message in one of its conversations | | `message_updated` | a message changed | | `conversation_opened` | a conversation opens (or reopens) | | `conversation_status_changed`, `conversation_updated` | its status or attributes change | | `conversation_resolved` | it's resolved | | `webwidget_triggered` | a visitor opened the live chat on your website | The payloads are the same as [Texa Desk webhooks](/guides/desk-webhooks#what-arrives). While a bot is connected, new conversations start as **pending**: they're the bot's until it hands them over. ## 3. Answer Reply with the bot's token in `api_access_token`: ```bash curl -X POST https://desk.texadesk.com/api/v1/accounts/1/conversations/171/messages \ -H "api_access_token: $BOT_TOKEN" -H "Content-Type: application/json" \ -d '{"content": "Your card is ready at our Lekki branch.", "message_type": "outgoing"}' ``` Only reply to `incoming` messages, so the bot never answers itself. ## 4. Hand over to people Set the conversation **open**, and Texa Desk assigns it to your team as usual: ```bash curl -X POST https://desk.texadesk.com/api/v1/accounts/1/conversations/171/toggle_status \ -H "api_access_token: $BOT_TOKEN" -H "Content-Type: application/json" \ -d '{"status": "open"}' ``` A bot's token can also change priority, typing status and assignment, but only on the conversations of its own inboxes. ## When the bot fails If the bot's webhook answers an error or doesn't answer, Texa Desk opens the pending conversation for your team, so a customer is never left waiting on a broken bot. An account can choose to keep such conversations pending instead. --- # Dashboard apps Source: https://developers.texadesk.com/desk/dashboard-apps A **dashboard app** is a page of yours that Texa Desk shows in a tab beside each conversation: the customer's orders, their account, their loan schedule. Your team sees it without leaving the conversation. ## 1. Add it In Texa Desk, **Settings → Integrations → Dashboard apps → Add dashboard app**: a title and your page's URL (`https`). It appears as a tab on every conversation, for everyone in the account. ## 2. Get the context When the tab opens, Texa Desk sends your page the context with `postMessage`. Ask for it again at any time by posting `chatwoot-dashboard-app:fetch-info` to the parent window. ```html ``` | Field | What | |---|---| | `conversation` | the open conversation: its id, inbox, status, labels, custom attributes, messages | | `contact` | its contact: `id`, `name`, `email`, `phone_number`, `identifier`, `custom_attributes` | | `currentAgent` | the person looking: `id`, `name`, `email` | | `customAttributes` | the account's custom attribute definitions | | `theme` | `light` or `dark`, sent again when it changes, so your page can match | ## Keep it safe - **Check `event.origin`** is your Texa Desk address before trusting the message. - The context says who is looking, but your page is still yours: protect it with your own sign-in (or a short-lived signed link your server makes), because anyone could open its URL directly. - Show only what your team needs: the dashboard app is visible to everyone in the account. --- # Tickets Source: https://developers.texadesk.com/desk/tickets A **ticket** is a request that needs following up: a disputed transfer, a complaint, a delivery that didn't arrive. It has a reference the customer can quote (`AUD-1001`), a type, a state, a promise ("resolved within 14 days"), and it gathers every conversation about it, on any channel. Conversations stay where they are; the ticket ties them together. ## The pieces | | What it is | |---|---| | **Reference** | the account's letters and the ticket's number: `AUD-1001`. API paths use the number (`1001`) | | **Type** | what kind of request (Complaint, Card request, Refund), with its own fields, promise, team and priority. A **complaint** type counts for regulators; a **sensitive** type opens to the customer only once they're verified | | **State** | in your team's words and the customer's ("In progress" / "We're working on it"), in one of four categories: `submitted`, `in_progress`, `waiting_on_customer`, `resolved` | | **Promise** | when the type promises to acknowledge and resolve, in hours, calendar days or business days. `due_at` is when it's due; past it and not resolved means **overdue** | | **Conversations** | every conversation about it. `main_conversation_id` is where it started | | **Tasks** | work another team does for it, with only the fields and files you share | | **Trackers** | one incident many customers write about ("card payments failing"): tickets on a tracker are told together and resolved together | ## Every conversation carries its ticket Every conversation in the API and in [webhooks](/guides/desk-webhooks) has a `ticket` field, `null` when it's on none: ```json "ticket": { "number": 1001, "reference": "AUD-1001", "title": "Refund for order 88", "kind": "ticket", "type": {"id": 2, "name": "Complaint", "complaint": true, "sensitive": true}, "state": {"name": "In progress", "customer_name": "We're working on it", "category": "in_progress"}, "assignee_id": 12, "team_id": 3, "priority": "high", "created_via": "texa_ai", "due_at": 1792800000, "resolved_at": null, "conversations_count": 2, "open_conversations_count": 1 } ``` Times are Unix seconds. `created_via` says who made it: `person`, `texa_ai`, `rule`, `inbox`, `customer` or `api`. ## Make one from your system ```bash curl -X POST https://desk.texadesk.com/api/v1/accounts/1/tickets \ -H "api_access_token: $TEXA_DESK_TOKEN" -H "Content-Type: application/json" \ -d '{"conversation_id": 171, "ticket_type_id": 2, "title": "Refund for order 88"}' ``` Without a conversation, give `contact_id` and how the request came in (`channel`: `branch`, `phone`, `letter`, `in_person`, `other`), for a complaint written at a branch, say. The customer is told the reference at once, on their channel, in your account's wording. ## Move it along ```bash # a state curl -X POST https://desk.texadesk.com/api/v1/accounts/1/tickets/1001/state \ -H "api_access_token: $TEXA_DESK_TOKEN" -H "Content-Type: application/json" \ -d '{"ticket_state_id": 9}' ``` - Moving into a **resolved** state asks for the type's required fields first; the refusal says which ("Fill in Order reference, Outcome first."). Fill them with `PATCH` and `custom_attributes`. - Resolving closes the conversations the person sees; `meta.left_open` counts those outside their view. - **Refusals** are `422 {"error": ""}`, in the person's language. ## Who can do what Tickets follow their conversations: a person sees a ticket when they see one of its conversations. Making and changing tickets needs `conversation_update`; merging, `ticket_merge`; trackers, `tracker_manage`; Settings → Tickets, `settings_tickets`; reports, `report_manage` or `report_team`. ## Events Subscribe a [Texa Desk webhook](/guides/desk-webhooks) to `ticket_created`, `ticket_updated` and `ticket_merged`. A change to a ticket also sends `conversation_updated` for each of its conversations. ## For regulators A complaint's register and a regulator's return (CBN, Bank of Ghana) download as CSV: [complaints register](/api-reference/desk-texa/ticket-reports/complaints-register-as-csv), [a regulator's return](/api-reference/desk-texa/ticket-reports/a-regulator-s-return-as-csv). ## Reference Every route: [Tickets API](/api-reference/desk-texa/tickets/list-tickets). Customers follow their own tickets through the [customer portal](/desk/customer-portal). --- # Customer portal Source: https://developers.texadesk.com/desk/customer-portal Your Texa Desk help centre has a **My requests** area: customers sign in with a code to their email or phone and see their tickets, each with every channel's public messages, and can reply or make a new request. It works on the help centre's pages with no code. This page is about doing the same **inside your own app**, with the JSON API under: ```text https://desk.texadesk.com/public/api/v1/portals/{slug}/texa/ ``` `{slug}` is your help centre's slug (Settings → Help centre). ## Sign the customer in **With a code.** The customer gives an email or a phone number; Texa sends a code; you exchange it for a session. ```bash curl -X POST https://desk.texadesk.com/public/api/v1/portals/help/texa/code \ -H "Content-Type: application/json" -d '{"address": "tolu@example.com"}' # {"sent": true} (for any address: it never tells who is a customer) curl -X POST https://desk.texadesk.com/public/api/v1/portals/help/texa/verify \ -H "Content-Type: application/json" -d '{"address": "tolu@example.com", "code": "482913"}' ``` ```json {"level": "code", "expires_at": 1790003600, "contact": {"id": 345, "name": "Tolu Bello"}, "token": "..."} ``` **With your own sign-in.** If the customer is already signed in to your app, your **server** proves who they are: an HMAC-SHA256 of their identifier with the HMAC token of your help centre's live chat inbox ([identity validation](/desk/live-chat#identity-validation)): ```bash curl -X POST https://desk.texadesk.com/public/api/v1/portals/help/texa/identity \ -H "Content-Type: application/json" \ -d '{"identifier": "user-1842", "identifier_hash": ""}' ``` That session is at the `verified` level. Send the `token` as `Authorization: Bearer ` on every call below. ## Their requests | Call | Does | |---|---| | `GET …/requests` | the customer's tickets: reference, title, type, state in the customer's words, dates | | `GET …/requests/{reference}` | one, with its customer fields and its public `messages` from every channel | | `POST …/requests/{reference}/messages` | a reply (`content`, up to 5 files in `attachments[]`) | | `GET …/request_types` | the kinds of request a customer can make, with their fields | | `POST …/requests` | a new request (`type_id`, `content`, `title`, `fields`, files): made and acknowledged at once | | `POST …/track` | a code sent to the address on file for a reference, for "track my request" without an account | - **Only public messages**: never a private note, an activity line or Texa AI's drafts. - **Sensitive requests** (every complaint) at the `code` level answer `open: false`, with empty `messages` and `fields`: show the reference, state and dates, and offer your own sign-in, which opens it in full. - **Limits** per customer: 30 messages an hour; 5 new requests an hour and 20 a day. Over them is `429`. - **Refusals** are `422 {"error": "..."}` in the help centre's language. `401` means signed out. - Every sign-in, refused code and view of a sensitive ticket is in your account's **audit log**. Every route, with its fields: [Customer portal API](/api-reference/portal/signing-in/send-a-sign-in-code). --- # Texa AI inside your helpdesk Source: https://developers.texadesk.com/desk/other-helpdesks !!! note "Early access" These connections are built and tested against each helpdesk's documented API, but not yet with every vendor's live accounts. Talk to [info@texadesk.com](mailto:info@texadesk.com) before you connect a production helpdesk; we'll do the first run with you. A company that already runs another helpdesk can keep it. Texa AI works inside it the way it works in Texa Desk: the same procedures, knowledge, actions, kill switch and credits. It answers as its own agent there, hands tickets to your groups with a note, and reports in Texa Desk. Set it up in Texa Desk under **Texa AI → Agent and inboxes → Other helpdesks**. First add Texa AI as a user of its own in your helpdesk (an agent such as `ai@yourcompany.com`), so its replies carry its name. ## How it behaves - **It starts in shadow mode:** for each customer message it posts a draft as an internal note, seen only by your team. When a person answers, the draft is scored against what they sent. - **Live mode** answers customers on the share of new tickets you choose. - **A person takes a ticket** by replying or assigning it to anyone else. Texa AI then says nothing to the customer. - **Giving it back:** assign the ticket to the Texa AI user, or add the tag `texa-ai` (where the helpdesk has tags). - **A handover** leaves an internal note with the summary and facts, unassigns the ticket, moves it to the mapped group with its priority, and tags it `ai-handover`. - **The kill switch** hands every ticket to people at once, with a note saying Texa AI is paused. - Webhooks are only a signal: Texa AI reads the ticket again through the helpdesk's API, so a forged or replayed webhook can cause at most a read. ## Per helpdesk | Helpdesk | Sign-in | Changes reach Texa by | Hand over to | |---|---|---|---| | **Zendesk** | Zendesk's OAuth (an API token for a pilot) | webhooks Texa sets up, signed | groups | | **Freshdesk** | the Texa AI agent's API key | two automation rules you add (the steps are shown), with a token | groups | | **Salesforce Service Cloud** | your org's connected app (client credentials, JWT or web sign-in) | a check every 20 seconds, optionally an outbound message | a queue (required) | | **Intercom** | Intercom's OAuth, or your workspace's own app | webhooks, signed | teams | | **HubSpot** | HubSpot's OAuth (a private app's token for a pilot) | webhooks, signed | ticket pipeline stages | | **Zoho Desk** | Zoho's OAuth, on your data centre | webhooks Texa sets up, with a secret address | departments | One helpdesk account belongs to one Texa Desk account: connecting the same Zendesk subdomain or Intercom workspace from a second account is refused. --- # Single sign-on Source: https://developers.texadesk.com/desk/sso An administrator sets it up in Texa Desk under **Settings → Security → Single sign-on**. | Provider | Protocol | |---|---| | Microsoft Entra ID | OpenID Connect, with PKCE, nonce and signature checks | | Google Workspace | OpenID Connect | | Any other (Okta, OneLogin, JumpCloud, ADFS…) | SAML 2.0, signed responses to Texa Desk's own requests only | ## Addresses your provider needs `{account_id}` is your account's number (in Texa Desk's address after `/accounts/`). | | Address | |---|---| | OpenID Connect redirect | `https://desk.texadesk.com/sso/{account_id}/oidc/callback` | | SAML assertion consumer service (ACS) | `https://desk.texadesk.com/sso/{account_id}/saml/acs` | | SAML metadata (entity id and certificates) | `https://desk.texadesk.com/sso/{account_id}/saml/metadata` | For OpenID Connect, give Texa Desk your app's client id and secret (stored encrypted). For SAML, give your provider's entity id, sign-in URL and signing certificate. ## Your domains Only your own email domains sign in through your SSO, and each must be proven: by Microsoft or Google on every sign-in, or, for SAML, by a DNS TXT record Texa Desk shows you. One domain belongs to one account. ## Optional - **Provisioning:** people who sign in through your provider for the first time get an account in Texa Desk. - **Enforce SSO:** password and "Sign in with Google" close for your team, except one named break-glass administrator, so you're never locked out. Every SSO sign-in and settings change is in your **audit log**. The Texa Desk app signs in through your SSO too. --- # The Texa Desk app Source: https://developers.texadesk.com/desk/mobile-app ## For your team The Texa Desk app is the helpdesk on a phone: every inbox, conversations, contacts, tickets and notifications, connected to `https://desk.texadesk.com`. It signs in with the same account, including your [single sign-on](/desk/sso). !!! note "In testing" The iPhone and Android app is in testing before it reaches the App Store and Google Play. Ask [info@texadesk.com](mailto:info@texadesk.com) to join the test. For developers, the app works on the same [Texa Desk API](/api-reference/desk) your integrations use: anything your integration changes shows in the app at once. ## For your customers To put Texa AI in **your own** customers' app, you don't need the Texa Desk app: | Your app | Use | |---|---| | React Native or Expo | [`@texadesk/react-native`](/sdks/react-native): the `TexaChat` component | | Flutter | [`texa_flutter`](/sdks/flutter) (coming soon) | | Native iOS, voice | [Swift SDK](/sdks/swift): `TexaVoiceLine` | | Anything else | your own screens on the [widget routes](/api-reference/texa-api/widgets/a-widget-session-for-one-customer-its-token-is-for-a-web-page-or-an-app), or the [live chat](/desk/live-chat#in-a-mobile-app) in a web view | Each gives signed-in customers the same Texa AI as your website, with your team in Texa Desk behind it. --- # API reference Source: https://developers.texadesk.com/api-reference/introduction Texa has one API for Texa AI and a family of APIs for Texa Desk. Pick by what you're building. | API | Base URL | Authenticates with | For | |---|---|---|---| | [Texa API](/api-reference/texa-api) | `https://api.texadesk.com/v1` | `Authorization: Bearer texa_live_…` | Texa AI in your own software: conversations, events, actions, voice, widgets | | [Texa Desk Application API](/api-reference/desk) | `https://desk.texadesk.com/api/v1` | `api_access_token: ` | the helpdesk: conversations, messages, contacts, inboxes, teams, reports, settings | | [Tickets API](/api-reference/desk-texa) | `https://desk.texadesk.com/api/v1` | the same | tickets, their types and states, tasks and reports | | [Client API](/api-reference/client) | `https://desk.texadesk.com/public/api/v1` | an inbox identifier and a contact identifier | a customer-facing chat on an API inbox | | [Customer portal API](/api-reference/portal) | `https://desk.texadesk.com/public/api/v1/portals/{slug}/texa` | a portal session | customers' own requests, in your app | | [Platform API](/api-reference/platform) | `https://desk.texadesk.com/platform/api/v1` | a platform app's token | accounts and people across a [dedicated instance](/hosting/dedicated) | ## OpenAPI documents Generate a client in any language from these: - **Texa API:** [https://api.texadesk.com/v1/openapi.json](https://api.texadesk.com/v1/openapi.json), always the live one ([a copy here](/openapi/texa-api.json)). - **Texa Desk**, every family above: [/openapi/texa-desk.json](/openapi/texa-desk.json). ## Conventions | | Texa API | Texa Desk APIs | |---|---|---| | Errors | `{"error": {"type", "code", "message", "field"}}` and `X-Request-Id` ([errors](/api-basics/errors)) | `{"error": "…"}` or `{"message", "errors"}`, a sentence you can show | | Ids | `cnv_…`, `evt_…`, your own `customer_ref` | numbers; a conversation by its display id, a ticket by its number | | Times | ISO 8601 | Unix seconds for tickets, ISO elsewhere | | Lists | cursors, `after` and `next` ([lists](/api-basics/pagination)) | pages, `page` and `meta` | | Retries | `Idempotency-Key` on any `POST` ([retries](/api-basics/idempotency)) | none: check before you send again | | Limits | per minute, per environment ([limits](/api-basics/rate-limits)) | per person and address | --- # Texa API Source: https://developers.texadesk.com/api-reference/texa-api ```text https://api.texadesk.com/v1 ``` Every request carries a Texa API key: ```http Authorization: Bearer texa_test_... ``` Make keys in Texa Desk under **Texa AI → Developers → API keys**. A `texa_test_…` key reaches your [sandbox](/guides/sandbox); `texa_live_…` your live account. Keep keys on your server: a page or an app uses a [widget session](/guides/website-and-app) instead, and the **widgets** routes below take that session's `wst_…` token. ## The shape of it 1. `POST /v1/conversations` opens a conversation for your `customer_ref`. 2. `POST /v1/conversations/{id}/messages` sends what the customer said. The answer's `events` hold Texa AI's `reply`, any `action_request`, a `handover` or a `close`. 3. Answer an action you run with `POST …/action-results`. 4. Hear about everything later on `GET /v1/events` and your [webhooks](/guides/webhooks). Start with the [quickstart](/quickstart). The reference below is generated from the [OpenAPI document](https://api.texadesk.com/v1/openapi.json), so it is always the API as it is. ## Groups | Group | Routes for | |---|---| | Conversations | opening, messages, action results, handover, handback, closing, assist | | Voice | single-use voice session tokens | | Records | transcripts, exports, quality scores | | Customers | what Texa AI remembers; merge; forget | | Events | the feed of every event | | Media | voice notes, photos, documents, video | | Agent | the agent your customers meet; the kill switch | | Actions | the actions Texa AI can ask for | | Knowledge | documents, with a person's approval | | Reports | support and quality reports | | Sandbox | fixtures for actions (test keys only) | | Widgets | sessions and the routes the website widget and app components call | | Account | the key in use; this month's usage | --- # Texa Desk Application API Source: https://developers.texadesk.com/api-reference/desk ```text https://desk.texadesk.com/api/v1/accounts/{account_id} ``` `{account_id}` is the number after `/accounts/` in Texa Desk's address. Every request carries an access token in the `api_access_token` header: ```bash curl https://desk.texadesk.com/api/v1/accounts/1/conversations \ -H "api_access_token: $TEXA_DESK_TOKEN" ``` - **A person's token** (Texa Desk → **Profile settings → Access token**) acts as that person, with their role's permissions and only the inboxes and teams they see. For an integration, add a person for it with a custom role that has only what it needs. - **An agent bot's token** works on the conversations of the bot's inboxes: messages, status, assignment. See [agent bots](/desk/agent-bots). Texa Desk is built on the open-source Chatwoot helpdesk, and this API keeps its shape, so tools written for it work with a Texa Desk address and token. Texa's own additions are the [Tickets API](/api-reference/desk-texa), the [customer portal](/api-reference/portal), and the `ticket` on every conversation. ## Webhooks The events of all of this arrive as [Texa Desk webhooks](/guides/desk-webhooks). --- # Tickets API Source: https://developers.texadesk.com/api-reference/desk-texa ```text https://desk.texadesk.com/api/v1/accounts/{account_id} ``` The same access token as the [Application API](/api-reference/desk), in `api_access_token`. A ticket is addressed by its **number** (`1001` in `AUD-1001`), a conversation by its display id. Read [tickets](/desk/tickets) first for what a ticket, a type, a state and a promise are, and who may do what. Refusals are `422 {"error": ""}` in the person's language. --- # Client API Source: https://developers.texadesk.com/api-reference/client ```text https://desk.texadesk.com/public/api/v1/inboxes/{inbox_identifier} ``` The Client API is what a chat on the customer's device calls: it needs no key. It knows the inbox by its **inbox identifier** (the API inbox's Configuration tab) and the customer by the **contact identifier** returned when you create the contact. The contact identifier is that customer's key to their conversations: keep it on their device, or behind their sign-in. Turn on the inbox's identity validation so a contact for one of your users is made only with an `identifier_hash` your server signed. Walkthrough: [bring your own channel](/desk/api-channel). --- # Customer portal API Source: https://developers.texadesk.com/api-reference/portal ```text https://desk.texadesk.com/public/api/v1/portals/{slug}/texa ``` Sign the customer in with a code to their address (`…/code`, then `…/verify`) or with your own sign-in (`…/identity`), then send the session's `token` as `Authorization: Bearer `. Walkthrough: [customer portal](/desk/customer-portal). --- # Platform API Source: https://developers.texadesk.com/api-reference/platform ```text https://desk./platform/api/v1 ``` !!! note "Dedicated instances only" The Platform API manages a whole installation. On Texa's shared hosting it isn't open to companies; it is there on a [dedicated instance](/hosting/dedicated), whose super admin makes platform apps. A **platform app**'s token goes in the `api_access_token` header. A platform app reaches only the accounts, people and agent bots it created (or was given), so an outside system that provisions accounts can't touch the others. Use it to create an account and its first administrator from your own sign-up, or to give someone a single sign-on link (`GET /platform/api/v1/users/{id}/login`). --- # Survey pages Source: https://developers.texadesk.com/api-reference/other When a conversation is resolved, Texa Desk can ask the customer to rate it. The survey page is public: it's addressed by the conversation's uuid, which only the customer was sent. --- # SDKs Source: https://developers.texadesk.com/sdks Every SDK is a thin client over the same [Texa API](/api-reference/texa-api): one method per route, errors that carry the request's id, an **action handler** that runs Texa AI's action requests with your functions, and a **webhook check**. None of them retries behind your back: to retry safely, send the same `Idempotency-Key` or `message_id` again ([why](/api-basics/idempotency)). Each passes the same contract tests against the Texa API. | Language | Install | Runs on | |---|---|---| | [Python](/sdks/python) | `pip install texa` | your server; sync and async | | [JavaScript and TypeScript](/sdks/typescript) | `npm install @texadesk/sdk` | Node 20+, Deno, Bun, edge runtimes | | [React Native and Expo](/sdks/react-native) | `npm install @texadesk/react-native` | your customers' app: the chat component | | [Go](/sdks/go) | `go get github.com/texadigital/texa-go` | your server; standard library only | | [Swift](/sdks/swift) | Swift Package Manager: `https://github.com/texadigital/texa-swift` | server-side Swift; voice in iOS apps | | [PHP](/sdks/php) | `composer require texa/texa-php` | PHP 8.1+: Laravel, Symfony, plain PHP | | [Java and Kotlin](/sdks/java) | coming soon | Java 17+ | | [Flutter](/sdks/flutter) | coming soon | your customers' app | The source of every SDK is public at [github.com/texadigital/texa-sdks](https://github.com/texadigital/texa-sdks), MIT licensed. Report a problem there. ## Server SDKs and widgets - **Server SDKs** hold your API key. They run on your server only. - **Widgets** run in a web page or an app and never hold a key. Your server makes a [widget session](/guides/website-and-app#2-signed-in-customers-your-server-says-who-they-are) for the customer and gives the widget its token: - [`widget.js`](/sdks/widget) for websites, one script tag; - [`@texadesk/react-native`](/sdks/react-native) for React Native and Expo apps; - Flutter, coming soon. ## The same calls in every language | Route | Python | JavaScript | Go | PHP | |---|---|---|---|---| | `POST /v1/conversations` | `open_conversation` | `openConversation` | `OpenConversation` | `openConversation` | | `POST …/messages` | `send_message` | `sendMessage` | `SendMessage` | `sendMessage` | | `POST …/action-results` | `action_result` | `actionResult` | `ActionResult` | `actionResult` | | `POST …/handover`, `…/handback`, `…/close` | `handover`, `handback`, `close` | the same | `Handover`, `Handback`, `Close` | the same | | `POST …/human-messages` | `human_message` | `humanMessage` | `HumanMessage` | `humanMessage` | | `POST …/voice-sessions` | `voice_session` | `voiceSession` | `VoiceSession` | `voiceSession` | | `GET …/transcript`, `…/export`, `…/quality` | `transcript`, `export`, `quality` | `transcript`, `exportConversation`, `quality` | `Transcript`, `Export`, `Quality` | `transcript`, `export`, `quality` | | `POST /v1/widget-sessions` | `widget_session` | `widgetSession` | `WidgetSession` | `widgetSession` | | `GET /v1/events` | `events` (all pages), `events_page` | `eventsPage` | `Events`, `EventsPage` | `events`, `eventsPage` | | Customers | `get_customer`, `merge_customer`, `forget_customer` | `getCustomer`, `mergeCustomer`, `forgetCustomer` | `GetCustomer`, `MergeCustomer`, `ForgetCustomer` | the same as JavaScript | | Sandbox fixtures | `put_fixture`, `list_fixtures`, `delete_fixture` | `putFixture`, `deleteFixture` | `PutFixture`, `DeleteFixture` | `putFixture`, `listFixtures`, `deleteFixture` | | Run action requests | `run_actions` | `runActions` | `RunActions` | `runActions` | | Check a webhook | `webhooks.parse` | `parseWebhook` | `ParseWebhook` | `Texa\Webhooks::parse` | ## The action handler `run_actions` (`runActions`) takes the answer to a request and a function per action. For each action request Texa didn't already run (`handled_by` is not set), it calls your function with the inputs, gives Texa the result, and collects what Texa AI says next. Return the action's data, or `{"status": "failed", "error": "..."}`. Throwing is a failure too, which Texa AI tells the customer honestly. Actions with no function are left for you. --- # Python Source: https://developers.texadesk.com/sdks/python ```bash pip install texa ``` Python 3.10+. The package is [`texa` on PyPI](https://pypi.org/project/texa/). ## Use it ```python from texa import Client, webhooks texa = Client() # reads TEXA_API_KEY; or Client(key="texa_test_...") convo = texa.open_conversation(channel="web", customer_ref="user-1842", idempotency_key="open-user-1842") out = texa.send_message(convo["conversation_id"], "Where is my transfer?", message_id="msg-551") events = texa.run_actions(convo["conversation_id"], out, { "get_transfer_status": lambda inputs: {"status": "paid_out"}, }) replies = [e["text"] for e in events if e["type"] == "reply"] ``` `AsyncClient` has the same methods, awaited; its action handlers may be async: ```python from texa import AsyncClient texa = AsyncClient() convo = await texa.open_conversation(channel="web", customer_ref="user-1842") ... await texa.aclose() # when your app shuts down ``` ## Webhooks ```python # in your webhook endpoint, with the raw body (FastAPI shown) @app.post("/texa/webhooks") async def texa_webhook(request: Request): event = webhooks.parse(WEBHOOK_SECRET, request.headers, await request.body()) ... # raises if the signature is wrong or old ``` ## Widget sessions ```python @app.post("/texa/session") def texa_session(user = Depends(signed_in_or_401)): return texa.widget_session(f"user-{user.id}", first_name=user.first_name, name=user.full_name, email=user.email) ``` ## Errors A failed request raises `texa.TexaError`, with `status`, `type`, `code`, `message`, `field` and `request_id` (quote it to support), and `retry_after` on a 429. See [errors](/api-basics/errors). ## Methods `me`, `usage`, `widget_session`, `open_conversation`, `list_conversations`, `get_conversation`, `send_message`, `action_result`, `human_message`, `handover`, `handback`, `close`, `assist`, `voice_session`, `transcript`, `export`, `quality`, `get_customer`, `merge_customer`, `forget_customer`, `upload_media`, `get_media`, `media_content`, `get_agent`, `get_kill_switch`, `list_actions`, `get_action`, `list_documents`, `add_document`, `events`, `events_page`, `support_report`, `quality_report`, `put_fixture`, `list_fixtures`, `delete_fixture`, `run_actions`. Each matches a route in the [reference](/api-reference/texa-api). --- # JavaScript and TypeScript Source: https://developers.texadesk.com/sdks/typescript ```bash npm install @texadesk/sdk ``` No dependencies: Node 20+, Deno, Bun and edge runtimes (Cloudflare Workers, Vercel). Types are included. The package is [`@texadesk/sdk` on npm](https://www.npmjs.com/package/@texadesk/sdk). **Keep it on your server**: it holds your key. For a page or an app, use [the widgets](/sdks/widget). ## Use it ```ts import { TexaClient, parseWebhook } from "@texadesk/sdk"; const texa = new TexaClient({ key: process.env.TEXA_API_KEY! }); const convo = await texa.openConversation({ channel: "web", customer_ref: "user-1842" }, "open-user-1842"); const out = await texa.sendMessage(convo.conversation_id, { text: "Where is my transfer?", message_id: "msg-551" }); const events = await texa.runActions(convo.conversation_id, out, { get_transfer_status: async (inputs) => ({ status: "paid_out" }), }); ``` ## Webhooks ```ts // Express: keep the raw body for the signature app.post("/texa/webhooks", express.raw({ type: "application/json" }), async (req, res) => { const event = await parseWebhook(process.env.TEXA_WEBHOOK_SECRET!, req.headers, req.body); res.sendStatus(200); queue.push(event); }); ``` `verifyWebhook` checks without parsing; `signWebhook` signs a body, for your tests. ## Widget sessions ```ts app.post("/texa/session", requireSignIn, async (req, res) => { res.json(await texa.widgetSession(`user-${req.user.id}`, { first_name: req.user.firstName, email: req.user.email, })); }); ``` ## Errors A failed request throws `TexaError` with `status`, `type`, `code`, `message`, `field` and `requestId` (and `retryAfter` on a 429). See [errors](/api-basics/errors). --- # Go Source: https://developers.texadesk.com/sdks/go ```bash go get github.com/texadigital/texa-go ``` The standard library only. Source: [github.com/texadigital/texa-go](https://github.com/texadigital/texa-go). ```go import texa "github.com/texadigital/texa-go" c, _ := texa.New("", "") // key from TEXA_API_KEY, the default address convo, _ := c.OpenConversation(ctx, texa.M{"channel": "web", "customer_ref": "user-1842"}, "open-user-1842") out, _ := c.SendMessage(ctx, convo.ConversationID, texa.M{"text": "Where is my transfer?", "message_id": "msg-551"}) events, _ := c.RunActions(ctx, convo.ConversationID, out, map[string]texa.ActionHandler{ "get_transfer_status": func(ctx context.Context, in texa.M) (texa.M, error) { return texa.M{"status": "paid_out"}, nil }, }) ``` ## Webhooks ```go func webhook(w http.ResponseWriter, r *http.Request) { raw, _ := io.ReadAll(r.Body) event, err := texa.ParseWebhook(os.Getenv("TEXA_WEBHOOK_SECRET"), r.Header, raw) if err != nil { http.Error(w, "bad signature", http.StatusUnauthorized) return } w.WriteHeader(http.StatusOK) go handle(event) } ``` ## Methods `Me`, `Usage`, `WidgetSession`, `OpenConversation`, `ListConversations`, `GetConversation`, `SendMessage`, `ActionResult`, `HumanMessage`, `Handover`, `Handback`, `Close`, `Assist`, `VoiceSession`, `Transcript`, `Export`, `Quality`, `GetCustomer`, `MergeCustomer`, `ForgetCustomer`, `GetMedia`, `MediaContent`, `GetAgent`, `GetKillSwitch`, `ListActions`, `ListDocuments`, `AddDocument`, `Events`, `EventsPage`, `PutFixture`, `DeleteFixture`, `RunActions`. Every method takes a `context.Context` first. --- # PHP Source: https://developers.texadesk.com/sdks/php ```bash composer require texa/texa-php ``` PHP 8.1+ with the curl and json extensions. The package is [`texa/texa-php` on Packagist](https://packagist.org/packages/texa/texa-php). ```php $texa = new Texa\Client(getenv('TEXA_API_KEY')); $convo = $texa->openConversation(['channel' => 'web', 'customer_ref' => 'user-1842'], idempotencyKey: 'open-user-1842'); $out = $texa->sendMessage($convo['conversation_id'], ['text' => 'Where is my transfer?', 'message_id' => 'msg-551']); $events = $texa->runActions($convo['conversation_id'], $out, [ 'get_transfer_status' => fn (array $inputs) => ['status' => 'paid_out'], ]); ``` ## Webhooks ```php $event = Texa\Webhooks::parse(getenv('TEXA_WEBHOOK_SECRET'), getallheaders(), file_get_contents('php://input')); ``` ## Laravel: a widget session ```php Route::post('/texa/session', fn () => auth()->check() ? $texa->widgetSession('user-' . auth()->id(), ['name' => auth()->user()->name]) : response()->json(['error' => 'not signed in'], 401)); ``` Errors throw `Texa\TexaError`, with the status, code and request id. --- # Swift Source: https://developers.texadesk.com/sdks/swift Add `https://github.com/texadigital/texa-swift` with Swift Package Manager, from 1.0.0: ```swift .package(url: "https://github.com/texadigital/texa-swift", from: "1.0.0") ``` ## On a server **`TexaClient` holds a key: use it on a server** (server-side Swift, such as Vapor), never in an app. ```swift import Texa let texa = try TexaClient() // the key from TEXA_API_KEY let convo = try await texa.openConversation(["channel": "web", "customer_ref": "user-1842"]) ``` `TexaWebhooks.parse(...)` checks a webhook's signature. ## In an iOS app: voice Use **`TexaVoiceLine`** with the single-use token your server got from [`voiceSession`](/guides/voice), so the app never holds a key. It handles `ready`, audio, captions, `flush` and `ended`; you call `open`, `send` (microphone audio), `say` (a typed turn in text mode) and `hangup`. Capture and playback stay with your app (AVAudioEngine, with echo cancellation on). --- # Java and Kotlin Source: https://developers.texadesk.com/sdks/java !!! note "Coming soon" The Java SDK is written and passes the contract tests, but isn't on Maven Central yet, so the dependency below doesn't resolve today. Until it is, call the [API](/api-reference/texa-api) over HTTP. This is how the client will look. ```xml com.texadesk texa-java 1.0.0 ``` ```java TexaClient texa = new TexaClient(System.getenv("TEXA_API_KEY")); var convo = texa.openConversation(Map.of("channel", "web", "customer_ref", "user-1842"), "open-user-1842"); String id = (String) convo.get("conversation_id"); var out = texa.sendMessage(id, Map.of("text", "Where is my transfer?", "message_id", "msg-551")); var events = texa.runActions(id, out, Map.of("get_transfer_status", in -> Map.of("status", "paid_out"))); var event = Webhooks.parse(secret, timestampHeader, signatureHeader, rawBody); ``` Java 17+, and Kotlin with the same client. --- # Website widget (widget.js) Source: https://developers.texadesk.com/sdks/widget ```html ``` The widget has no dependencies and lives in its own shadow DOM, so your styles never reach it. It never holds a key. The full walkthrough is [Texa AI in your website and app](/guides/website-and-app). This is **Texa AI's** chat. For Texa Desk's **live chat** (answered by your team, with Texa AI if you switch it on), see [live chat](/desk/live-chat). ## Attributes | Attribute | What | |---|---| | `data-site-key` | your site key (Texa AI → Developers → Website and app), for visitors who haven't signed in | | `data-session-url` | a route on **your** server: `POST` answers a [widget session](/api-reference/texa-api/widgets/a-widget-session-for-one-customer-its-token-is-for-a-web-page-or-an-app) when someone is signed in, `401` when not | | `data-title` | the chat's title | | `data-greeting` | the first line visitors see | | `data-color` | your colour for the button and the customer's messages | | `data-voice` | `true` adds a **Talk** button: a voice call in the browser | | `data-files` | `false` hides the paper clip | With only `data-session-url`, the chat is for signed-in customers only; with only `data-site-key`, for visitors only. ## JavaScript The script loads `async`: call these after the `texa:ready` event, or set `window.TexaWidgetSettings = { handlers: {...}, onOpenPage: fn }` before it loads. | Call | Does | |---|---| | `TexaWidget.open()`, `TexaWidget.close()` | opens or closes the chat | | `TexaWidget.signedIn()` | after your own sign-in in a single-page app: the same chat carries on as the customer | | `TexaWidget.signedOut()` | after sign-out: forgets the chat on this browser | | `TexaWidget.on(action, fn)` | a screen action: `fn(inputs)` does it on the page and returns its results, within 30 seconds | | `TexaWidget.onOpenPage(fn)` | `fn(url, page)` when Texa AI takes the customer to a page; by default the page loads | ```js window.addEventListener("texa:ready", () => { TexaWidget.onOpenPage((url) => router.push(url)); TexaWidget.on("fill_delivery_address", async ({ street, city }) => { form.street.value = street; form.city.value = city; return { filled: true }; }); }); ``` ## Content security policy If your site sends a CSP, allow `https://api.texadesk.com` in `script-src` and `connect-src`, and `wss://voice.texadesk.com` in `connect-src` for voice. --- # React Native and Expo Source: https://developers.texadesk.com/sdks/react-native ```bash npm install @texadesk/react-native ``` The package is [`@texadesk/react-native` on npm](https://www.npmjs.com/package/@texadesk/react-native). It never holds a key: visitors use your **site key**, and signed-in customers a **widget session** your server makes. ## The chat ```tsx import { TexaChat } from "@texadesk/react-native"; (user ? await api.post("/texa/session") : null)} signedIn={!!user} title="Help" actions={{ fill_delivery_address: async (inputs) => ({ filled: true }) }} onOpenPage={(url, page) => navigation.navigate(page)} onPickFile={pickDocument} /> ``` | Prop | What | |---|---| | `siteKey` | your site key, for visitors who haven't signed in | | `getSession` | returns your server's widget session for the signed-in customer, or `null` | | `signedIn` | whether someone is signed in now: change it and the chat carries on as them | | `actions` | screen actions: steps your app does for the customer, by name ([how](/guides/website-and-app#5-pages-and-steps-on-your-page)) | | `onOpenPage` | Texa AI takes the customer to a page: navigate there | | `onPickFile` | your file picker, returning `{ uri, name, type }`, for the paper clip | | `onOpenDocument` | a document Texa AI sent (a receipt, a spreadsheet as CSV) | | `title`, `greeting`, `color` | how it looks | | `apiUrl`, `pollMs` | for testing; leave the defaults | The chat follows the phone's light or dark setting, masks card numbers, PINs and codes in the typing preview your team sees, and shows your team's answers when they take over. ## Your own screens `TexaWidgetClient` is the same chat without a UI, for your own design, and `TexaVoiceLine` opens a voice session whose audio you wire to your app's audio module (16-bit PCM, echo cancellation on). `maskDraft` is the typing-preview mask, if you send previews yourself. --- # Flutter Source: https://developers.texadesk.com/sdks/flutter !!! note "Coming soon" `texa_flutter` is written and tested but isn't on pub.dev yet. This is how the component will look. Until then, a [web view of the live chat](/desk/live-chat#in-a-mobile-app) or your own screens on the [widget routes](/api-reference/texa-api) work today. ```dart TexaChat( siteKey: 'pk_live_...', getSession: () async => user == null ? null : WidgetSession.fromJson(await api.texaSession()), signedIn: user != null, actions: {'fill_delivery_address': (inputs) async => {'filled': true}}, onOpenPage: (url, page) => Navigator.pushNamed(context, url), ) ``` The same props as the [React Native component](/sdks/react-native), with `TexaWidgetClient` for your own screens and `TexaVoiceLine` for voice. --- # Where your data lives Source: https://developers.texadesk.com/hosting Texa Desk is a hosted service: you don't install or run it. What you choose is **where** it runs. | Option | What it is | For | |---|---|---| | **Standard hosting** | Texa's shared deployment at `texadesk.com` | most companies | | **In-country hosting** | Texa's deployment in Nigeria, shared by the companies that need it. Servers, databases, files and backups stay in the country | companies with a residency rule, such as payments data under the CBN's circular | | **A dedicated instance** | one company's own Texa Desk, databases and backups, on servers in the country it chooses, on its own domains | banks and others who need their own instance | In-country hosting and dedicated instances are set up on request: write to [info@texadesk.com](mailto:info@texadesk.com). See [dedicated instances](/hosting/dedicated). ## What lives where | Part | Keeps | |---|---| | Texa Desk | conversations, contacts, files, tickets, your team's work | | Texa's service layer (the API, connectors, billing) | references and facts rather than copies of conversations; sealed secrets; logs of what it did; 24 hours of API answers for [idempotency](/api-basics/idempotency) | | Texa AI | transcripts, what it remembers about each customer, call recordings | | The AI models | nothing kept: they read each message to answer it | Every account sees the exact map for its own deployment in Texa Desk, under **Texa AI → Safety → Where your data lives**: where each part runs, and where the AI models process. An account can require a country; its go-live checklist then fails, in plain words, while any part is elsewhere. The outside services Texa uses, and what each receives, are listed in the [privacy notice](https://texadesk.com/privacy). ## Data that never leaves your network For lookups in systems whose data may not leave your network at all (a core banking database), the [Texa Relay](/guides/relay) runs inside it and answers only signed, read-only, masked operations, with no inbound port. --- # Dedicated instances Source: https://developers.texadesk.com/hosting/dedicated A dedicated instance is a complete Texa Desk for one company: its own Texa Desk, service layer, databases, files and backups, on servers in the country it chooses, under its own domains. Texa runs and updates it. Ask [info@texadesk.com](mailto:info@texadesk.com) to plan one. ## What changes for developers Nothing in the APIs. The same routes and SDKs work; only the addresses are yours: | | Texa's hosting | Your instance (say `pilotbank.ng`) | |---|---|---| | Texa Desk and its API | `https://desk.texadesk.com` | `https://desk.pilotbank.ng` | | Texa API | `https://api.texadesk.com` | `https://api.pilotbank.ng` | | Webhooks into Texa | `https://hooks.texadesk.com` | `https://hooks.pilotbank.ng` | | Voice sessions | `wss://voice.texadesk.com` | `wss://voice.pilotbank.ng` | | The Texa Relay | given per company | `relay.pilotbank.ng` | Point the SDKs at your address: ```python Python texa = Client(base_url="https://api.pilotbank.ng") ``` ```js JavaScript const texa = new TexaClient({ key: process.env.TEXA_API_KEY!, baseUrl: "https://api.pilotbank.ng" }); ``` ```go Go c, _ := texa.New("", "https://api.pilotbank.ng") ``` ## Only on a dedicated instance - **The [Platform API](/api-reference/platform):** create accounts, people and agent bots across the whole instance, with a platform app's token from the instance's super admin. - **Your own sign-in rules** for the whole instance, and its own audit trail. ## Texa AI on a dedicated instance Texa AI's engine and its models can run for the instance in the same country, on request. Until they do, the instance's **Texa AI → Safety** page says where they run, so your compliance team sees it plainly. --- # Status Source: https://developers.texadesk.com/hosting/status [https://status.texadesk.com](https://status.texadesk.com) shows whether each part of Texa is working now, checked every minute, with the last 90 days and Texa's notes on any incident. | Part | Checks | |---|---| | Texa API | it answers, and its database answers | | Texa AI | its engine is ready | | Texa Desk | it answers | | Webhooks | deliveries aren't waiting behind | Each day of the 90 is a bar: working, slow or partly working for a few minutes, not working for a few minutes, or grey when nothing was recorded. Runs of three minutes or more that a part wasn't working are listed as outages, found by Texa's own checks, beside the notes Texa's team posts while it works on an incident (looking into it, cause found, fixed and watching, resolved). ## For machines ```bash curl https://status.texadesk.com/status.json ``` ```json {"status": "operational", "components": {"api": "operational", "texa_ai": "operational", "texa_desk": "operational", "webhooks": "operational"}, "checked_at": 1790000000} ``` Each component is `operational`, `degraded` or `down`; `status` is the worst of them. `/status.json?history=1` adds `history` (per part, a day by day count of minutes checked, degraded and down, and its `uptime` over 90 days), `outages` and `incidents`. It answers any origin, so a dashboard of yours can read it from the browser. The same page and JSON are on the API's own domain, at [https://api.texadesk.com/status](https://api.texadesk.com/status). If something is wrong and the status page says it's working, write to [info@texadesk.com](mailto:info@texadesk.com) with a request id (`X-Request-Id`). --- # Security Source: https://developers.texadesk.com/hosting/security Short answers for your security review. Ask [info@texadesk.com](mailto:info@texadesk.com) for the full security pack. ## Sign-in and access - **Multi-factor authentication** (an authenticator app, with backup codes) for each person. - **[Single sign-on](/desk/sso)** with Microsoft Entra ID, Google Workspace or any SAML 2.0 provider, only for your own proven domains, optionally enforced. - Texa's own staff always sign in in two steps. - Sign-in attempts and password resets are rate-limited. - **Roles:** administrators, agents, and custom roles with only the permissions you give. People see only their own inboxes and teams. ## Your keys - Texa API keys are shown **once**; only a hash is kept. Revoke one and it stops at once. - Widgets never hold a key: your server makes a short-lived session for one customer, valid only on the websites you list. - Everything Texa sends you is **signed** (HMAC-SHA256 with a timestamp). [How to check](/authentication#checking-what-texa-sends-you) ## Isolation and encryption - Every company's data is kept apart by row-level security in the database, tested on every table. - Every secret Texa keeps for you (your API keys for connectors, webhook secrets, helpdesk keys) is sealed with AES-256-GCM, under keys outside the database, and never written to a log. - TLS for everything in transit; connectors call only `https` in live. ## What Texa AI may do - **Only actions you connected**, only in a step of a procedure a named person approved. - **Changes need the customer's yes**, and verification first where the action says so. - **Private fields** (an amount, an account number) are never said to an unverified customer. - **Failures are told as failures:** Texa AI never claims an action succeeded when it didn't. - Connectors never reach a private address (no server-side request forgery), and follow no redirects. - Card numbers, PINs, passwords, CVVs and verification codes are masked before any AI model reads them. ## Stopping it Switch Texa AI off at once: for the whole account (**Texa AI → Safety**), for one inbox, one helpdesk connection, one Relay or one key. Conversations go to your team. ## Audit Settings → Audit logs records sign-ins, settings changes, SSO, and every view of a sensitive ticket; Texa AI's settings changes are recorded with who made them. Every action call is recorded with its result, with sensitive values hidden. ## Privacy Texa follows the Nigeria Data Protection Act 2023. You can export a conversation, and [forget a customer](/guides/customers#forget-a-customer) on request. The services Texa uses and what each receives are in the [privacy notice](https://texadesk.com/privacy). When a company closes its account, its data is deleted: [closing an account](/hosting/account-closure). ## Reporting a vulnerability Write to [info@texadesk.com](mailto:info@texadesk.com) with what you found and how to reproduce it. Please don't test against other companies' accounts or real customers. --- # Closing an account Source: https://developers.texadesk.com/hosting/account-closure A company asks Texa to close its account through support (the chat at [texadesk.com](https://texadesk.com), or [info@texadesk.com](mailto:info@texadesk.com)). ## On the day it closes - Texa AI goes off on every inbox, and running campaigns stop. - **API keys, site keys and webhook endpoints are revoked**: requests answer `401`, and nothing more is sent to your servers. - The company's administrators get an email with the date its data will be deleted, and how to undo it. ## Fourteen days later Unless Texa reopens the account in those 14 days at the company's request, its data is deleted: conversations, contacts, files, Texa AI's memory of its customers, and the account's settings. Backups age out within the following 14 days. Kept, because the law asks: - **payment records** for six years (tax law); - **complaint records**, without the customers' details, for the regulator's period. ## Before you close Export what you need: conversations through the [Texa Desk API](/api-reference/desk) or Texa Desk's own exports, the complaints register through the [Tickets API](/api-reference/desk-texa/ticket-reports/complaints-register-as-csv), and the [event feed](/api-basics/pagination#the-event-feed) of your Texa API conversations. --- # Install the Texa Relay Source: https://developers.texadesk.com/hosting/relay-install !!! note "On request" Texa sets up each company's Relay address and gives you the program (a Linux binary, or a container image). Ask [info@texadesk.com](mailto:info@texadesk.com) to start. The Texa Relay lets Texa AI look things up in your systems (a transfer's status, an account's state, a customer's accounts) **without your data or credentials leaving your network** and **without opening any inbound port**. It runs on a server of yours, connects *out* to Texa over mutual TLS, and runs only the operations your administrator has written, reviewed and signed. ## What it can and cannot do - It runs **only the named operations in your signed file**: a `SELECT` your team wrote, or a call to one of your internal services. Texa sends an operation's name and values for its declared parameters, never SQL, a URL or a header. - **Queries must be read-only.** Each is checked to be a single `SELECT`. The database login must be read-only; the Relay checks this at start and refuses to run otherwise. Every query runs in a read-only transaction. - **Limits you set:** rows, time, response size, requests a minute, and how many run at once. - **Masking:** account numbers cut to the last four digits, card numbers to the first six and last four, BVN and NIN hashed, dates of birth to the year. Set per column, or by column name across all queries. - **Customer checks:** an operation can require that Texa has verified the customer first. A customer's own phone number or email comes from Texa's record of who is writing, never from what the AI chose. - **An audit log on your disk:** every request and its outcome (not the data returned), hash-chained so a changed or removed line shows. `texa-relay audit` verifies it. - **A kill switch on your side:** create the file `/var/lib/texa-relay/KILL`, or send `SIGUSR1`. Every running request stops, and nothing runs until the file is removed. Texa cannot lift your switch. Texa has its own switch, which cannot lift yours. ## What you need - A Linux server or VM (2 vCPU, 1 GB of memory is plenty), or any Docker host. - Outbound HTTPS (443) to the Relay address Texa gives you. Nothing inbound. A proxy is supported (`hub.proxy`). - A **read-only** login for each database, ideally on a reporting replica. - For internal services: their addresses and a token, in environment variables (never in the file). ## Setting it up 1. **Make your administrator's signing key.** Keep the private key off the Relay server, with the person who approves changes. ```bash texa-relay keygen --out ./relay-admin # admin.key (keep safe), admin.pub ``` 2. **Write the file** `/etc/texa-relay/relay.yaml`. Start from the template for your core banking system (`templates/finacle.yaml`, `templates/t24.yaml` or `templates/flexcube.yaml`). Confirm every table and service with your team, and delete what you do not allow. Connection strings come from environment variables (`dsn_env`), never from the file. A file with one operation looks like this (from the Finacle template): ```yaml serial: 1 name: pilotbank-finacle hub: {url: ""} databases: finacle: driver: oracle dsn_env: FINACLE_REPLICA_DSN # a read-only login on a reporting replica mask_columns: "*bvn*": hash "*nin*": hash queries: account_status: description: Whether an account is open, frozen or closed, and its scheme database: finacle sql: | SELECT foracid, acct_cls_flg, frez_code, schm_code FROM tbaadm.gam WHERE foracid = :account params: account: {type: text, pattern: "[0-9]{10}", description: the NUBAN} outputs: foracid: {type: text} acct_cls_flg: {type: text, description: Y when closed} frez_code: {type: text, description: "a freeze code, blank when none"} schm_code: {type: text} mask: {foracid: last4} requires_verified: true ``` Texa gives you the templates for Finacle, Temenos Transact (T24) and Oracle FLEXCUBE with the program. 3. **Sign it,** on the administrator's machine. Signing checks the file first and lists every problem at once; nothing is signed until there are none: ```bash texa-relay sign --key admin.key relay.yaml # writes relay.yaml.sig ``` Copy `relay.yaml`, `relay.yaml.sig` and `admin.pub` to `/etc/texa-relay/`. Every later change raises `serial` and is signed again. The Relay refuses an older serial, so a removed query cannot be brought back by restoring an old file. 4. **Enrol.** In Texa Desk, under **Texa AI → Actions → Relay**, add a relay. You get a one-time token that lasts 24 hours. On the Relay server: ```bash texa-relay enrol --hub --token ``` The Relay makes its own key on your server and asks Texa to sign a certificate. The key never leaves the server. Certificates last 30 days and renew by themselves. Then check everything the Relay checks when it starts (the signature, the file, each database login is read-only, the certificate), without serving anything: ```bash texa-relay check ``` 5. **Run it,** as a service: ```ini # /etc/systemd/system/texa-relay.service [Service] ExecStart=/usr/local/bin/texa-relay run EnvironmentFile=/etc/texa-relay/secrets.env User=texa-relay Restart=always RestartForceExitStatus=75 ``` or in Docker: ```bash docker run -d --name texa-relay --restart always \ -v /etc/texa-relay:/etc/texa-relay:ro -v texa-relay-state:/var/lib/texa-relay \ --env-file /etc/texa-relay/secrets.env ``` 6. **Bind operations to actions.** Texa Desk shows the relay connected, with the operations your signed file offers. Your team binds each one to the Texa AI action it answers. Texa routes nothing else. The first connection records the key your file is signed with. If a later file is signed with a **different** key, Texa refuses the relay until a person accepts the new key in Texa Desk, so a file signed by anyone else cannot slip in. ## Running it - `texa-relay audit` verifies the audit log's chain and names the first broken line. - Health (databases reachable, requests, refusals, the audit head) is sent to Texa every 15 seconds and shown in Texa Desk. - **Updates** are off unless your file turns them on (`update.enabled`). When on, the Relay installs only a release signed with Texa's release key built into it. It checks the release's hash, never downgrades, keeps the previous binary, and restarts. In Docker, pull the new image instead. - Exit status 75 means "updated, restart me". The systemd unit above does that. How the Relay is built, its protocol and its threat model are in the [security overview](/hosting/security); ask [info@texadesk.com](mailto:info@texadesk.com) for the full design document for your security review. --- # Changelog Source: https://developers.texadesk.com/changelog ## 19 Oct 2026 - **[status.texadesk.com](https://status.texadesk.com)**: each part checked every minute, the last 90 days, outages found by Texa's checks and incident notes; `status.json?history=1` for machines. - **Websites as knowledge**: up to 10,000 pages a site (was 30), the first 30 at once and the rest in the background. - **developers.texadesk.com**: the developer documentation moves here from `api.texadesk.com/docs`, with every Texa Desk API in the reference, search, and [llms.txt](/llms.txt) for AI coding tools. The old addresses redirect. - **Tickets API and customer portal API** documented, with an [OpenAPI document](/openapi/texa-desk.json) covering every Texa Desk family. - **SDKs published**: Python (`pip install texa`), JavaScript and TypeScript (`@texadesk/sdk`), React Native (`@texadesk/react-native`), Go, Swift and PHP, all at 1.0.0. Source at [github.com/texadigital/texa-sdks](https://github.com/texadigital/texa-sdks). Java and Flutter are coming soon. - **[Texa AI in your website and app](/guides/website-and-app)**: visitor chats from a site key, signing in mid-chat, the customer's own token for your API, files and documents both ways, pages Texa AI can open, screen actions, and the masked typing preview for your team. - **Actions from a documentation page**: no OpenAPI file needed. Texa AI drafts actions from your API's documentation, for you to review. - **[WordPress plugin](/desk/wordpress)** for Texa Desk's live chat. - **Visitor sessions** answer a wrong site key with `401` and name the field of a malformed request. - **Live chat**: the website code loads `sdk.js?v=2`. Older code keeps working. ## 18 Oct 2026 - **Texa Desk is live at texadesk.com**: `desk.texadesk.com`, `api.texadesk.com`, `hooks.texadesk.com` and `voice.texadesk.com`. Every guide and SDK uses these addresses. - **[Live chat guide](/desk/live-chat)**: installing on any website builder, `window.$texa` commands, identity validation with server code, events. ## 17 Oct 2026 - **[Tickets](/desk/tickets)**: references, types, states, promises, tasks for other teams, merges, links and trackers, complaints registers and regulators' returns, on the Texa Desk API and in webhooks (`ticket_created`, `ticket_updated`, `ticket_merged`). - **[Customer portal](/desk/customer-portal)**: customers sign in with a code and follow their requests, in the help centre or your app.