Hosted APIs: your users, whole applications, summaries, writes, and an audience you choose; a conversation you close through the API records that you closed it

Hosted APIs: your users, whole applications, summaries, writes, and an audience you choose

A hosted API can now serve an application end to end. Everything below is on /v1/apis and on the host each API answers at, for workspaces granted hosted APIs.

Your users, verified. auth_mode: user_token accepts a short-lived JWT your own backend signs for each user (HS256 with a secret minted by POST /v1/apis/{api_id}/rotate-user-token-secret, or a JWKS URL you register). Routes bind the verified claims as {{user.sub}}, the response cache is keyed per person, and a filter on user_identity may only bind from that claim, never from the request.

A whole application behind one route. A route whose path ends in * serves a published tree: file_root names the prefix, file_index the entry document a bare prefix and a deep link answer, so a client-routed application survives a refresh and a rebuild that renames its assets touches no route.

A summary in one request. POST /v1/stores/{store_id}/collections/{collection}/documents/aggregate answers count, sum, avg, min and max, optionally grouped by one field, from the same where a query takes. The same reduction is a run tool (store_aggregate) and a hosted resolver (store_aggregate). Aggregation is the last operation the query surface takes; no joins, nested filters, full-text or SQL follow.

A write from the edge. A store_write route lands the POST body as a document: write_mode create mints an id and answers 201, replace and merge land on document_id and answer 200. Never on a public API. On an API that names its caller, the document is stamped user_identity and only that person can change it afterwards. Send an Idempotency-Key and a retry replays the first answer.

Bounds in one family. daily_read_cap (429 route_read_limit_reached) and daily_write_cap (429 route_write_limit_reached) join daily_run_cap; a cache hit is not a read and a replayed write is not a write. A public GET route with no cache TTL is served with a 60 second floor. GET /v1/apis/{api_id}/usage shows today’s reads, runs and writes against each cap, per route.

An audience, narrowest first. auth_mode now takes owner (only the API’s owner, with their own Speechify API key or console session) and workspace (any member of the owning workspace, the same way) beside user_token, consumer_key and public. A workspace can refuse internet-facing APIs with hosted_apis_public_allowed on PATCH /v1/workspaces/current; a create or a switch to public is then answered 403 hosted_api_public_refused. APIs already public keep answering.

Served from the edge. *.apis.speechify.ai is served by its own replicas behind a CDN that honours each route’s Cache-Control, so a customer’s traffic scales on its own and never contends with the control plane.

Every new field is optional on the wire and every existing API keeps answering exactly as before. The end-to-end walkthrough is Build a dashboard on the platform alone.

A conversation you close through the API records that you closed it

POST /v1/agents/conversations/{conversation_id}/end now records end_reason: operator_ended on the conversation it closes, so a conversation somebody deliberately ended can be told apart from one the agent or the caller ended.

Before this, a closed conversation was indistinguishable from an ordinary ending. A text or Slack conversation settled with end_reason: null, and a voice call settled with caller_hangup from the post-call fallback that runs when nothing else names a reason - so reporting on how conversations finish counted a deliberate close as the caller walking away.

operator_ended is the reason for that endpoint only. The agent’s own end_call still records agent_ended, and a caller who goes away still records caller_hangup. It reads back on the Conversation resource wherever you fetch one - GET /v1/agents/conversations/{conversation_id}, the list, the conversation.completed webhook - and as an end-reason filter and breakdown in agent analytics.

Existing conversations are not backfilled: rows closed this way before today keep the reason they were written with.