Spoken confirmations ("Your order is ready for pickup") are easy to add, and easy to get wrong by shipping an API key in front-end code. Here is a small pattern that keeps the key on your server.

I work on Speakvora, a speech API. It is in early access (free allowance, billing opens at launch), and this post is mainly about the pattern; the same shape works with any text-to-speech API that returns an audio link.

The idea

The browser never talks to the speech API. It asks your server to speak a sentence; your server calls the API with the key and sends back only the audio URL.

1. Server route (Node 18+, Express)

import express from "express"; const app = express(); app.use(express.json()); app.post("/api/speak", async (req, res) => { const text = String(req.body.text || "").slice(0, 500); const r = await fetch("https://speakvora.com/api/v1/speak", { method: "POST", headers: { "x-api-key": process.env.SPEAKVORA_KEY, "content-type": "application/json" }, body: JSON.stringify({ text, voice: "af_heart", lang: "en" }), }); const j = await r.json(); res.status(r.status).json(r.ok ? { url: j.url } : { error: j.error }); }); app.listen(3000); 

2. Browser

async function say(text) { const r = await fetch("/api/speak", { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify({ text }) }); const j = await r.json(); if (!r.ok) return console.error(j.error); new Audio(j.url).play(); // must follow a user gesture such as a click } document.querySelector("#go").onclick = () => say("Welcome back."); 

3. The errors worth handling

  • 402 free_allowance_used: the free allowance is spent.
  • 429 rate_limited: wait retry_after_seconds and retry.
  • 404 not_in_library: that voice only plays pre-recorded library lines; pick a live voice.

Things I would do next

  • Show a loading state. Phrases already in the library come back almost instantly; a brand-new sentence is generated and takes longer.
  • Keep the returned URL and reuse it rather than asking for the same sentence again.
  • Put a length limit and a rate limit on your own /api/speak route, because it spends your quota.

Full version with the Python and cURL variants: https://speakvora.com/guides/add-speech-to-a-web-app?utm_source=devto&utm_medium=article&utm_campaign=tutorial-add-speech

Free key (no card): https://speakvora.com/dashboard. Questions: support@eyeguide.ai