POST /api/tutor
Returns one complete answer as JSON. This is the simplest way to call OORR and the right default for server-to-server use.
curl -X POST https://api.oorr.ai/api/tutor \
-H "Content-Type: application/json" \
-d '{
"userId": "student_4821",
"message": "وما وحدة قياسها؟",
"plan": "normal",
"modelTier": "basic",
"conversationId": "conv_7f3a",
"history": [
{ "role": "user", "content": "اشرح قانون أوم بإيجاز" },
{ "role": "assistant", "content": "قانون أوم ينص على أن..." }
]
}'
type TutorResponse = {
answer: string;
/** Always "sargon-1.5". */
model: string;
inputTokens?: number;
outputTokens?: number;
totalTokens?: number;
latencyMs: number;
requestId: string;
modelTier?: "basic" | "pro" | "ultra";
};
async function ask(message: string, history: { role: "user" | "assistant"; content: string }[]) {
const res = await fetch("https://api.oorr.ai/api/tutor", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
userId: "student_4821",
message,
plan: "normal",
modelTier: "basic",
conversationId: "conv_7f3a",
history,
}),
});
if (res.status === 429) {
const retry = Number(res.headers.get("Retry-After") ?? 30);
throw new Error(`rate limited, retry in ${retry}s`);
}
return (await res.json()) as TutorResponse;
}
import requests
def ask(message, history):
res = requests.post(
"https://api.oorr.ai/api/tutor",
json={
"userId": "student_4821",
"message": message,
"plan": "normal",
"modelTier": "basic",
"conversationId": "conv_7f3a",
"history": history,
},
timeout=90,
)
if res.status_code == 429:
retry = int(res.headers.get("Retry-After", 30))
raise RuntimeError(f"rate limited, retry in {retry}s")
res.raise_for_status()
return res.json()
Response
200 OK
{
"answer": "وحدة قياس المقاومة هي الأوم (Ω)، وتُعرَّف بأنها...",
"model": "sargon-1.5",
"inputTokens": 412,
"outputTokens": 735,
"totalTokens": 1147,
"latencyMs": 4210,
"requestId": "0f4c1b86-27a1-4f1e-9d0a-2b7c6e5a1d33",
"modelTier": "basic"
}
Streaming
POST /api/tutor/stream
Identical request body. Validation and rate-limit failures are still ordinary
JSON HTTP errors; once the request is accepted the response becomes
text/event-stream.
| Event | Data | Meaning |
|---|---|---|
ready | {} | Stream opened. |
status | { stage, detail?, sequence } | Progress frame from a closed allowlist of stages. Carries no answer text. |
delta | { text } | An answer fragment. Concatenate in order. |
trial | Pro-trial status | Emitted for pro requests. |
done | Full response object | Terminal. Same shape as the JSON endpoint, including token counts. |
error | { error, code, requestId } | Terminal failure after the stream opened. |
status.stage is one of accepted,
context, intent, preparing,
alternate_path, retrying,
quality_check, answer_ready.
detail appears only for context
(fresh | followup) and intent
(concise, explain_more, quiz,
image, general).
event: ready
data: {}
event: status
data: {"stage":"accepted","sequence":1}
event: status
data: {"stage":"context","detail":"fresh","sequence":2}
event: delta
data: {"text":"قانون أوم "}
event: delta
data: {"text":"ينص على أن..."}
event: done
data: {"answer":"قانون أوم ينص على أن...","model":"sargon-1.5","inputTokens":318,"outputTokens":602,"totalTokens":920,"latencyMs":3870,"requestId":"9a2e...","modelTier":"basic"}
const res = await fetch("https://api.oorr.ai/api/tutor/stream", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
userId: "student_4821",
message: "اشرح قانون أوم بإيجاز",
plan: "normal",
modelTier: "basic",
}),
});
if (!res.ok) throw new Error((await res.json()).code);
const reader = res.body.getReader();
const decoder = new TextDecoder();
let buffer = "";
let answer = "";
while (true) {
const { value, done } = await reader.read();
if (done) break;
buffer += decoder.decode(value, { stream: true });
const frames = buffer.split("\n\n");
buffer = frames.pop() ?? "";
for (const frame of frames) {
const event = frame.match(/^event: (.+)$/m)?.[1];
const data = JSON.parse(frame.match(/^data: (.+)$/m)?.[1] ?? "{}");
if (event === "delta") answer += data.text;
if (event === "done") console.log(data.totalTokens, "tokens");
if (event === "error") throw new Error(data.code);
}
}
import json
import requests
with requests.post(
"https://api.oorr.ai/api/tutor/stream",
json={
"userId": "student_4821",
"message": "اشرح قانون أوم بإيجاز",
"plan": "normal",
"modelTier": "basic",
},
stream=True,
timeout=90,
) as res:
res.raise_for_status()
event = None
answer = ""
for line in res.iter_lines(decode_unicode=True):
if line is None or line == "":
event = None
continue
if line.startswith("event: "):
event = line[7:]
elif line.startswith("data: "):
data = json.loads(line[6:])
if event == "delta":
answer += data["text"]
elif event == "done":
print(data["totalTokens"], "tokens")
elif event == "error":
raise RuntimeError(data["code"])
oorr / api platform