GET /api/v1/org
Org name, room code, listener URL, plan, allowance
curl https://acts2.prebabel.ai/api/v1/org \
-H "Authorization: Bearer $ACTS2_KEY"Developers · Integrations
A caption overlay for your livestream, signed webhooks for your systems, and a small REST API. Included on every plan, including the free trial.
A transparent web page that shows live captions in one language. It joins your room like a listener, captions only — no audio, no buttons, nothing to click. It hides itself whenever the service is not live.
https://acts2.prebabel.ai/overlay/HK7CMN?lang=cmn&style=lower-third&bg=transparent&size=2&lines=2
Copy your ready-made URL from Account › Integrations (or the host app’s Settings › Integrations).
| Parameter | Values | Default |
|---|---|---|
lang | Any room language, e.g. cmn, yue, en, or orig (the speaker’s own words) | cmn |
style | lower-third (max 2 lines, centred) · full (caption screen) | lower-third |
bg | transparent · black | transparent |
size | 1 · 2 · 3 | 2 |
lines | 1–2 for lower-third, 1–3 for full | 2 |
script | hans (简) · hant (繁) — Chinese only | as sent |
bg=black with style=full for a confidence monitor or an overflow-room screen.Also works with vMix (Web Browser input) and Ecamm Live (Overlay › Web).
Get told when a service starts, pauses, resumes and ends — and receive the full transcript after it ends. Up to 3 https endpoints per organisation, set by an admin in Account › Integrations.
| Event | data |
|---|---|
service.started | {service_id, source, targets, started_at} |
service.paused | {service_id, at} |
service.resumed | {service_id, at} |
service.ended | {service_id, started_at, ended_at, billable_seconds, peak_listeners, end_reason} |
transcript.ready | {service_id, languages: {yue: "…", cmn: "…"}} — final captions joined, sent after the service ends |
Every event is a JSON body {id, type, created, org_id, data}, sent as a POST:
POST /your/endpoint
content-type: application/json
Acts2-Event-Id: evt_01J9Z6Q4T8
Acts2-Signature: t=1760436000,v1=5f2b…c9e1
{
"id": "evt_01J9Z6Q4T8",
"type": "service.ended",
"created": 1760436000,
"org_id": "7c1e…",
"data": {
"service_id": "b3f0…",
"started_at": "2026-10-12T02:00:11Z",
"ended_at": "2026-10-12T03:31:40Z",
"billable_seconds": 3180,
"peak_listeners": 128,
"end_reason": "host"
}
}Acts2-Signature: t=<unix>,v1=<hex HMAC-SHA256(secret, "<t>.<raw body>")>, Acts2-Event-Id, content-type: application/json.2xx counts as delivered. 10 s timeout. Retries with backoff: 1 min, 5 min, 30 min, 2 h, 12 h.transcript.ready, then deleted (at the latest after 7 days).id.Compute the HMAC over the raw body before parsing it, compare in constant time, and reject old timestamps.
import crypto from "node:crypto";
// rawBody: the exact request body string (verify before JSON.parse)
export function verifyActs2(rawBody, signatureHeader, secret, toleranceSec = 300) {
const parts = Object.fromEntries(
String(signatureHeader).split(",").map((kv) => kv.trim().split("=", 2)),
);
const t = Number(parts.t);
if (!Number.isFinite(t) || Math.abs(Date.now() / 1000 - t) > toleranceSec) return false;
const expected = crypto.createHmac("sha256", secret).update(`${t}.${rawBody}`).digest("hex");
const a = Buffer.from(expected, "utf8");
const b = Buffer.from(String(parts.v1 ?? ""), "utf8");
return a.length === b.length && crypto.timingSafeEqual(a, b);
}
// Express: app.post("/acts2", express.raw({ type: "application/json" }), (req, res) => {
// if (!verifyActs2(req.body.toString("utf8"), req.get("Acts2-Signature"), process.env.ACTS2_WEBHOOK_SECRET))
// return res.sendStatus(400);
// const event = JSON.parse(req.body); // dedupe on event.id (Acts2-Event-Id)
// res.sendStatus(204);
// });import hashlib, hmac, time
def verify_acts2(raw_body: bytes, signature_header: str, secret: str, tolerance: int = 300) -> bool:
try:
parts = dict(kv.strip().split("=", 1) for kv in signature_header.split(","))
t = int(parts["t"])
except (KeyError, ValueError):
return False
if abs(time.time() - t) > tolerance:
return False
signed = f"{t}.".encode() + raw_body
expected = hmac.new(secret.encode(), signed, hashlib.sha256).hexdigest()
return hmac.compare_digest(expected, parts.get("v1", ""))
# Flask:
# @app.post("/acts2")
# def acts2():
# if not verify_acts2(request.get_data(), request.headers.get("Acts2-Signature", ""), SECRET):
# abort(400)
# event = request.get_json() # dedupe on event["id"]
# return "", 204Read your organisation, service history and live room status from your own tools.
Authenticate with an org API key: Authorization: Bearer acts2_sk_… Admins create and revoke keys in Account › Integrations; a key is shown once. Rate limit: 60 requests per minute per key.
GET /api/v1/orgOrg name, room code, listener URL, plan, allowance
curl https://acts2.prebabel.ai/api/v1/org \
-H "Authorization: Bearer $ACTS2_KEY"GET /api/v1/services?limit=&before=Service history, newest first
curl "https://acts2.prebabel.ai/api/v1/services?limit=10" \
-H "Authorization: Bearer $ACTS2_KEY"GET /api/v1/services/:idOne service
curl https://acts2.prebabel.ai/api/v1/services/SERVICE_ID \
-H "Authorization: Bearer $ACTS2_KEY"GET /api/v1/rooms/currentLive status, listeners by language
curl https://acts2.prebabel.ai/api/v1/rooms/current \
-H "Authorization: Bearer $ACTS2_KEY"Errors use {"error": {"code", "message"}} with a matching HTTP status (401 bad key, 404 not found, 429 rate limited).
Questions about an integration? Email support — we read every message.