API reference
Every endpoint is prefixed with https://api-v2.tanyalahustaz.com/api/v1.
/healthPublicHealth check. No authentication required.
Response
{ "status": "ok", "version": "v1", "timestamp": "..." }/meAuth requiredReturns the authenticated account profile and API key metadata.
Response
{
"partner": { "id": "uuid", "email": "...", "company_name": "..." },
"api_key": { "id": "uuid", "name": "Production", "last_used_at": null }
}/chatAuth requiredMain integration endpoint. Sends a user message, retrieves relevant knowledge from the Tanyalah Ustaz library, and returns an AI reply with sources.
Request body
{
"message": "Can a traveler combine Dhuhr and Asr?",
"category": "fiqh",
"session_id": "your-user-session-id"
}Response
{
"reply": "Travelers may combine Dhuhr with Asr...",
"session_id": "your-user-session-id",
"sources": [{ "slug": "jamak-solat-musafir", "title": "...", "category": "fiqh" }]
}
// Stream tokens as they arrive (text/event-stream):
// { "message": "...", "stream": true }
data: {"type":"meta","session_id":"...","sources":[...]}
data: {"type":"text","content":"Travelers "}
data: {"type":"text","content":"may combine..."}
data: {"type":"done"}/chat/sessionsAuth requiredList conversation sessions for your account, newest activity first. Supports ?limit and ?offset.
Response
{
"data": [{
"session_id": "your-user-session-id",
"turn_count": 3,
"created_at": "...",
"updated_at": "...",
"last_user_message": "Can a traveler combine Dhuhr and Asr?"
}],
"pagination": { "limit": 20, "offset": 0, "total": 1 }
}/chat/sessionsAuth requiredDelete all chat history for your account. Requires ?confirm=all.
Response
{ "deleted": true, "deleted_turns": 12 }/chat/sessions/:sessionIdAuth requiredGet chronological chat history for a session. Pagination limit/offset apply to conversation turns (each turn is one user + one assistant message).
Response
{
"session_id": "your-user-session-id",
"data": [
{ "id": "...-user", "role": "user", "content": "...", "created_at": "..." },
{ "id": "...-assistant", "role": "assistant", "content": "...", "sources": [...], "created_at": "..." }
],
"pagination": { "limit": 20, "offset": 0, "total": 3 }
}/chat/sessions/:sessionIdAuth requiredDelete one chat session and all of its turns.
Response
{
"deleted": true,
"session_id": "your-user-session-id",
"deleted_turns": 3
}/usageAuth requiredUsage stats for the current API key over the last 30 days.
Response
{
"period_days": 30,
"total_requests": 12,
"top_endpoints": [{ "endpoint": "/api/v1/chat", "count": 8 }],
"recent_requests": [...]
}/openapi.jsonPublicOpenAPI 3.1 specification for this API. No authentication required.
Response
{ "openapi": "3.1.0", "info": { "title": "Tanyalah Ustaz Developer API", "version": "1.0.0" }, ... }Streaming responses
Add "stream": true to POST /chat to receive the reply as server-sent events (SSE) instead of waiting for the full JSON. The first event carries the session_id and sources; text events follow as tokens arrive. This is the recommended way to make your UI feel fast.
Event shapes
data: {"type":"meta","session_id":"...","sources":[...]}
data: {"type":"text","content":"Travelers "}
data: {"type":"text","content":"may combine..."}
data: {"type":"done"}Browser example
Read the stream and append each text chunk:
const response = await fetch("https://api-v2.tanyalahustaz.com/api/v1/chat", {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: "Bearer tlh_live_...",
},
body: JSON.stringify({ message: "Can a traveler combine prayers?", stream: true }),
});
const reader = response.body!.getReader();
const decoder = new TextDecoder();
let buffer = "";
while (true) {
const { done, value } = await reader.read();
if (done) break;
buffer += decoder.decode(value, { stream: true });
const parts = buffer.split("\n\n");
buffer = parts.pop() ?? "";
for (const part of parts) {
const line = part.split("\n").find((entry) => entry.startsWith("data:"));
if (!line) continue;
const event = JSON.parse(line.slice(5).trim());
if (event.type === "text") appendToUi(event.content);
if (event.type === "done") finish();
}
}