One intake. Three calls. A clinician-ready result.
The Auddax API runs a governed clinical intake. You create an intake, you send each patient message as a turn, and you read the clinician handoff when the intake completes. The engine decides the questions, the safety status, and the disposition. Your application owns the surface.
The model is simple. An intake is one patient conversation. A turn is one patient message and the engine's reply. The handoff is the structured clinical summary the intake produces. Send patient language in. Get structured clinical state out.
https://api.auddax.ai
Three rules keep your integration safe. Relay assistant_message to the patient without edits.
Never override safety_status or disposition. Stop sending turns when
terminal is true.
From key to handoff in three calls.
You need an API key. Create an account in the developer console and generate one in a few clicks, or request one by email. Export it before you run the examples.
Step 1 · Create an intake
Create the encounter first. The response gives you the encounter_id you use on every later
call. The optional fields seed clinical context before the first turn.
$ curl -X POST https://api.auddax.ai/v1/intakes \
-H "Authorization: Bearer $AUDDAX_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"demographics": { "age_years": 58, "sex": "male" },
"patient_history": "Allergies: penicillin. Current medications: lisinopril."
}'
import os, httpx
BASE = "https://api.auddax.ai"
HEADERS = {"Authorization": f"Bearer {os.environ['AUDDAX_API_KEY']}"}
intake = httpx.post(
f"{BASE}/v1/intakes",
headers=HEADERS,
json={
"demographics": {"age_years": 58, "sex": "male"},
"patient_history": "Allergies: penicillin. Current medications: lisinopril.",
},
timeout=60,
).json()
encounter_id = intake["encounter_id"]
const BASE = "https://api.auddax.ai";
const HEADERS = {
Authorization: `Bearer ${process.env.AUDDAX_API_KEY}`,
"Content-Type": "application/json",
};
const intake = await fetch(`${BASE}/v1/intakes`, {
method: "POST",
headers: HEADERS,
body: JSON.stringify({
demographics: { age_years: 58, sex: "male" },
patient_history: "Allergies: penicillin. Current medications: lisinopril.",
}),
}).then((r) => r.json());
const encounterId = intake.encounter_id;
{
"encounter_id": "enc_auddax-sandbox_91a81eb05e4c58fa",
"status": "open",
"created_at": "2026-08-21T16:31:04.118Z",
"expires_at": "2026-08-21T18:31:04.000Z"
}
Send each patient message as one turn. The engine returns the next patient-facing message and the current clinical state. This example triggers a cannot-miss pathway, so the turn is terminal and the handoff is included.
$ curl -X POST https://api.auddax.ai/v1/intakes/$ENCOUNTER_ID/turns \
-H "Authorization: Bearer $AUDDAX_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: turn-1-$ENCOUNTER_ID" \
-d '{
"message": "Chest pressure for the last hour, sweating and short of breath."
}'
turn = httpx.post(
f"{BASE}/v1/intakes/{encounter_id}/turns",
headers={**HEADERS, "Idempotency-Key": f"turn-1-{encounter_id}"},
json={"message": "Chest pressure for the last hour, sweating and short of breath."},
timeout=240,
).json()
print(turn["assistant_message"])
if turn["terminal"]:
handoff = turn["handoff"]
const turn = await fetch(`${BASE}/v1/intakes/${encounterId}/turns`, {
method: "POST",
headers: { ...HEADERS, "Idempotency-Key": `turn-1-${encounterId}` },
body: JSON.stringify({
message: "Chest pressure for the last hour, sweating and short of breath.",
}),
}).then((r) => r.json());
console.log(turn.assistant_message);
const handoff = turn.terminal ? turn.handoff : null;
{
"encounter_id": "enc_auddax-sandbox_91a81eb05e4c58fa",
"turn_index": 1,
"assistant_message": "I need you to call 911 right now or have someone take you
to the emergency room immediately. [...]",
"choices": [],
"terminal": true,
"urgent": true,
"safety_status": "cannot_miss_positive",
"disposition": "urgent_escalation",
"disposition_label": "Emergency evaluation",
"handoff": { "soap": { "...": "..." }, "provenance": { "...": "..." } }
}
A benign message behaves differently. The engine asks the next question, terminal stays false,
and choices can carry quick replies you can render as buttons.
The handoff stays readable by encounter_id after the intake closes and after the session
expires. Store the id, not the payload, if you want to fetch it later.
$ curl https://api.auddax.ai/v1/intakes/$ENCOUNTER_ID/handoff \
-H "Authorization: Bearer $AUDDAX_API_KEY"
handoff = httpx.get(
f"{BASE}/v1/intakes/{encounter_id}/handoff",
headers=HEADERS,
timeout=60,
).json()
print(handoff["soap"]["assessment"])
const handoff = await fetch(`${BASE}/v1/intakes/${encounterId}/handoff`, {
headers: HEADERS,
}).then((r) => r.json());
console.log(handoff.soap.assessment);
The full handoff shape is documented in the handoff object.
Typed clients for Python and TypeScript.
The official SDKs cover the full API surface with typed requests, responses, and errors. Streaming,
retries, idempotency keys, and webhook signature verification are built in, so what you test is the
contract, not your HTTP plumbing. Both clients read AUDDAX_API_KEY from the environment.
$ pip install auddax # Python 3.10+
$ npm install auddax # Node 20+, zero dependencies
The whole quickstart above, through the SDK. A blocking turn attaches an Idempotency-Key
automatically, streaming is an iterator, and terminal turns embed the typed handoff.
from auddax import Auddax
client = Auddax()
intake = client.intakes.create(demographics={"age_years": 58, "sex": "male"})
with client.intakes.stream_turn(intake.encounter_id, "I have chest pressure and I'm sweating.") as stream:
for fragment in stream.text():
print(fragment, end="", flush=True)
if stream.turn.terminal:
print(stream.turn.handoff.soap.assessment)
import { Auddax } from "auddax";
const client = new Auddax();
const intake = await client.intakes.create({ demographics: { age_years: 58, sex: "male" } });
const stream = await client.intakes.streamTurn(intake.encounter_id, "I have chest pressure and I'm sweating.");
for await (const fragment of stream.text()) {
process.stdout.write(fragment);
}
if (stream.turn?.terminal) {
console.log(stream.turn.handoff?.soap?.assessment);
}
Batch evaluation is two calls: batches.create(scenarios) then
batches.wait(batch_id) for the full typed transcripts. Webhook deliveries verify with one
function, verify_signature(body, signature, secret). Errors raise typed exceptions:
RateLimitedError carries retry_after, IntakeClosedError means stop
sending turns. The raw HTTP examples throughout these docs remain valid; the SDKs are the same contract
with the plumbing handled.
One key. One header.
Every request except GET /v1/health needs your API key in the
Authorization header. Sandbox keys start with adx_sb_.
Authorization: Bearer adx_sb_0123456789abcdef0123456789abcdef01234567
Keep the key on your server. Do not put it in a browser, a mobile app, or a repository. A request with a
missing, wrong, or revoked key returns 401 unauthorized. If your key leaks, email
team@auddax.ai
and we rotate it. Revocation takes effect within one minute.
Your key sees only its own intakes. A request for another key's encounter_id returns
404 not_found.
The full surface.
- POST /v1/intakes Create an intake
- POST /v1/intakes/{encounter_id}/turns Send one patient message. Blocking or streaming.
- GET /v1/intakes/{encounter_id} Status summary
- GET /v1/intakes/{encounter_id}/handoff Clinician handoff. Works after close and expiry.
- POST /v1/webhooks Register an event endpoint. Also GET to list, DELETE to remove.
- POST /v1/batches Run scripted scenarios as a background job. GET for status.
- GET /v1/usage Today's usage against your quotas
- GET /v1/health Liveness and release identity. No auth.
- GET /v1/openapi.yaml The machine-readable spec
All request and response bodies are JSON in snake_case. Request bodies have a 64 KB limit. Every response
carries an x-auddax-release header with the release tag and build.
Create an intake · request fields
| Field | Type | Rules |
|---|---|---|
demographics.age_years |
integer | Optional. 0 to 130. |
demographics.sex |
string | Optional. One of female, male, intersex. |
patient_history |
string | Optional. Up to 4,000 characters. Free-text context such as allergies and current medications. |
Send a turn · request fields
| Field | Type | Rules |
|---|---|---|
message |
string | Required. 1 to 8,000 characters. The patient's message, unmodified. |
stream |
boolean | Optional. Default false. True returns Server-Sent Events. See streaming. |
Open, then closed or expired.
An intake opens when you create it. It closes when the engine reaches a terminal state. It expires when its session reaches the two-hour limit before a terminal state. The handoff stays readable in every state.
| Status | Meaning | Turns | Handoff |
|---|---|---|---|
open | The intake accepts turns. | Accepted | Readable |
closed | The engine reached a terminal state. | 409 intake_closed | Readable |
expired | The session passed the two-hour limit. | 410 intake_expired | Readable |
Check the state at any time with GET /v1/intakes/{encounter_id}. The summary includes the turn
count, the expiry time, and the last known safety status and disposition.
Every turn returns the clinical state.
A turn response always carries the same fields, terminal or not. The engine is authoritative. Show its message, honor its status, and do not soften its escalations.
| Field | Type | Meaning |
|---|---|---|
encounter_id | string | The intake this turn belongs to. |
turn_index | integer | The position of this turn, starting at 1. |
assistant_message | string | The next patient-facing message. Relay it without edits. |
choices | string[] | Engine-suggested quick replies. Render them as buttons if you want. |
terminal | boolean | True when the intake reached a terminal state. Stop sending turns. |
urgent | boolean | True when the engine requires immediate clinician review. Surface it prominently. |
safety_status | string | null | The deterministic safety state. Values include no_red_flags_yet, cannot_miss_negative, cannot_miss_positive. |
disposition | string | null | The engine's disposition. Values include continue_intake, clinician_review, self_care, urgent_escalation. |
disposition_label | string | null | A display label for the disposition. |
handoff | object | Present when terminal is true. The full handoff object. |
The safety fields can be null on a turn in one rare case: the turn completed but the snapshot read behind it
failed. The turn is still delivered. Read
GET /v1/intakes/{encounter_id}/handoff to get the state. Do not resend the turn.
Stream anything a patient can see.
A turn drives a real model turn inside the engine. The complete result takes 20 to 40 seconds. Streaming removes the wait a patient would feel. The reply arrives token by token within seconds, and your interface stays alive while the engine compiles the clinical snapshot behind it.
Set "stream": true on the turns endpoint. The response is
text/event-stream. Each event has an event: name and one
data: line of JSON.
$ curl -N -X POST https://api.auddax.ai/v1/intakes/$ENCOUNTER_ID/turns \
-H "Authorization: Bearer $AUDDAX_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "message": "It hurts when I swallow.", "stream": true }'
import json
with httpx.stream(
"POST",
f"{BASE}/v1/intakes/{encounter_id}/turns",
headers=HEADERS,
json={"message": "It hurts when I swallow.", "stream": True},
timeout=240,
) as stream:
event = None
for line in stream.iter_lines():
if line.startswith("event: "):
event = line[7:]
elif line.startswith("data: ") and event:
data = json.loads(line[6:])
if event == "message.delta":
print(data["text"], end="", flush=True)
elif event == "turn.completed":
turn = data
const res = await fetch(`${BASE}/v1/intakes/${encounterId}/turns`, {
method: "POST",
headers: HEADERS,
body: JSON.stringify({ message: "It hurts when I swallow.", stream: true }),
});
const reader = res.body.getReader();
const decoder = new TextDecoder();
let buffer = "";
let turn = null;
for (;;) {
const { done, value } = await reader.read();
if (done) break;
buffer += decoder.decode(value, { stream: true });
const blocks = buffer.split(/\r?\n\r?\n/);
buffer = blocks.pop() || "";
for (const block of blocks) {
const event = /^event: (.+)$/m.exec(block)?.[1];
const data = JSON.parse(/^data: (.+)$/m.exec(block)?.[1] || "{}");
if (event === "message.delta") render(data.text);
if (event === "turn.completed") turn = data;
}
}
Events
| Event | Data | Meaning |
|---|---|---|
message.delta | { "text" } | The next fragment of the patient-facing reply. Append it. |
message.completed | { "elapsed_ms" } | The reply text is complete. You may send the next turn now. |
handoff.compiling | { "terminal" } | The engine compiles the clinical snapshot. Terminal tells you the intake is closing. |
handoff.progress | { "chars" } | Compile progress. Useful for a subtle activity indicator. |
turn.completed | turn object | The same object the blocking response returns, handoff included when terminal. |
error | { "code", "message" } | The turn failed after the stream started. Read the handoff before you retry anything. |
This is a recorded sandbox turn, replayed with its real timing compressed. Press play to see the event order.
Recorded sandbox events. Timing compressed for the demo.
message.completed arrives, the
patient can answer. Send the next turn immediately. The engine accepts it while the previous turn's snapshot
still compiles. This is verified behavior, not an accident.
If a stream drops, do not resend the message blindly. The turn may have been delivered. Read the handoff to see the current state, then continue.
Plan for the latency budget.
Each turn runs a governed clinical reasoning pass. It is slower than a chat completion, and it is supposed to be. Design for the budget instead of hiding from it.
| Milestone | Typical, warm | What to show |
|---|---|---|
First message.delta | A few seconds | The reply, appearing as it streams. |
| Complete patient message | 20 to 40 seconds | The full reply. The patient can answer now. |
| Snapshot and handoff | Shortly after | Nothing. It compiles in the background. |
The gateway holds a turn open for up to 240 seconds before it fails the call. Set your client timeout at 240 seconds or higher. Use streaming for every patient-facing surface. Use blocking calls for server-side and batch work where nobody watches a spinner.
Get told when an intake ends.
Register an https endpoint and the API calls you when an intake reaches a terminal state. No polling. A webhook problem never fails or slows a turn.
$ curl -X POST https://api.auddax.ai/v1/webhooks \
-H "Authorization: Bearer $AUDDAX_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "url": "https://example.com/auddax/hook" }'
{
"id": "wh_9f2a51c07d3e44b1",
"url": "https://example.com/auddax/hook",
"events": ["intake.completed", "intake.escalated"],
"secret": "whsec_...shown once, store it...",
"created_at": "2026-08-21T18:20:11.312Z"
}
GET /v1/webhooks lists your endpoints (without secrets). DELETE /v1/webhooks/{id}
removes one. Each key holds up to 3 endpoints. The target must be a public https URL.
Events
| Event | Fires when |
|---|---|
intake.completed | An intake reaches a terminal state without an urgent escalation. |
intake.escalated | An intake ends with an urgent escalation. Treat it with priority. |
batch.completed | A batch run finishes. Arrives with the batch API. |
The delivery body is { id, event, created_at, data }. For intake events, data is
the same object the terminal turn returned, handoff included. Headers: auddax-event,
auddax-delivery, and auddax-signature.
Verify the signature
The signature header is t=<unix>,v1=<hex>. Compute HMAC-SHA256 over
t + "." + raw_body with your endpoint secret and compare. Reject stale timestamps.
const [, t, v1] = /t=(\d+),v1=([a-f0-9]+)/.exec(req.headers["auddax-signature"]);
const expected = crypto.createHmac("sha256", WEBHOOK_SECRET)
.update(`${t}.${rawBody}`)
.digest("hex");
const ok = crypto.timingSafeEqual(Buffer.from(v1), Buffer.from(expected))
&& Math.abs(Date.now() / 1000 - Number(t)) < 300;
Answer with a 2xx within 10 seconds. Anything else retries: 3 attempts with backoff from 30 seconds to
5 minutes. Deliveries are at-least-once, so make your handler idempotent on the
auddax-delivery id.
Evaluate the engine with scripted scenarios.
A batch runs synthetic patient conversations for you in the background. Send up to 20 scenarios with up to 12 messages each. The engine drives every scenario as a real intake, stops a scenario early when it reaches a terminal state, and stores the transcript. Use batches for benchmarking and regression checks.
$ curl -X POST https://api.auddax.ai/v1/batches \
-H "Authorization: Bearer $AUDDAX_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"label": "regression-aug",
"scenarios": [
{ "label": "benign", "messages": ["Sore throat since yesterday.", "It hurts when I swallow."] },
{ "label": "urgent", "messages": ["Chest pressure for an hour, sweating."],
"demographics": { "age_years": 58, "sex": "male" } }
]
}'
{ "batch_id": "bat_5f0c33a1b2d4e6f8", "status": "queued", "scenario_count": 2 }
Poll GET /v1/batches/{batch_id} for status and per-scenario results, or register a webhook for
batch.completed and skip polling. Each scenario result carries its
encounter_id; fetch the full handoff with the normal handoff endpoint.
GET /v1/batches lists your recent batches.
The rules that keep batches safe: batch turns draw from the same daily turn quota as
interactive calls, one batch runs at a time per key (a second create returns
409 batch_running), scenarios run with limited concurrency, and a quota-starved scenario is
marked failed with its partial transcript kept. Batch statuses: queued,
running, completed, completed_with_failures.
Evaluate a conversation you did not drive.
The intake endpoints drive the interview: the engine asks the questions and reaches a disposition.
Evaluate is the other shape. When a clinician runs the conversation and your product
only records and analyzes it, you do not want the engine asking questions. You want the part that has to
be right: catch the red flags and emergencies, evaluate the protocol, and decide the disposition over
whatever the conversation has surfaced so far. That is POST /v1/evaluate.
The division of labor is the whole point. Your model does perception only: it turns the
live transcript into the typed facts the engine needs, or you simply hand over the transcript text.
MedCanon is the authority: it, not your model, sets every red-flag state, the emergency
findings, and the disposition. No model runs inside the evaluate path, so the result is deterministic:
the same input gives the same output, and every response is stamped provenance.deterministic: true.
The scribe loop
A scribe records a growing clinician–patient conversation. On each new batch of speech, and once more at the end of the consult, you send the current state to evaluate and render what it returns.
2.
POST /v1/evaluate with the facts and/or the transcript.3. The engine returns the emergency screen, coverage, the protocol result, and the disposition.
4. Render the alerts and the disposition. Draft the note separately; the note is never the source of clinical truth. The orders and red-flag states come only from evaluate.
Call it about every 15 seconds while the consult is live, and once over the final transcript. It is fast because it is deterministic: no model turn, typically well under a second. It has its own generous rate class (at least 120 requests per minute) and does not draw from the intake turn quota.
Request
Send transcript_text, facts, or both. transcript_text is the
fastest way to integrate and drives the emergency screen; facts (with a
protocol_id) gives the precise rule verdict.
$ curl -X POST https://api.auddax.ai/v1/evaluate \
-H "Authorization: Bearer $AUDDAX_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"correlation_id": "consult-8f3a",
"input": {
"transcript_text": [
{ "speaker": "clinician", "text": "What brings you in today?" },
{ "speaker": "patient", "text": "The worst headache of my life, it hit like a thunderclap a minute ago." }
]
}
}'
import os, httpx
BASE = "https://api.auddax.ai"
HEADERS = {"Authorization": f"Bearer {os.environ['AUDDAX_API_KEY']}"}
result = httpx.post(
f"{BASE}/v1/evaluate",
headers=HEADERS,
json={
"correlation_id": "consult-8f3a",
"input": {
"transcript_text": [
{"speaker": "clinician", "text": "What brings you in today?"},
{"speaker": "patient", "text": "The worst headache of my life, thunderclap onset a minute ago."},
],
# or send extracted facts for a precise verdict:
# "protocol_id": "headache",
# "facts": {"sudden_onset_thunderclap": True, "has_nausea_or_vomiting": True},
},
},
timeout=30,
).json()
if result["emergency"]["urgent"]:
escalate(result) # render the alert; the engine, not your model, decided this
const res = await fetch("https://api.auddax.ai/v1/evaluate", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.AUDDAX_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
correlation_id: "consult-8f3a",
input: {
transcript_text: [
{ speaker: "clinician", text: "What brings you in today?" },
{ speaker: "patient", text: "The worst headache of my life, thunderclap onset a minute ago." },
],
},
}),
});
const result = await res.json();
if (result.emergency.urgent) escalate(result);
Request fields
| Field | Type | Rules |
|---|---|---|
correlation_id | string | Optional. For your own audit correlation only. The server keeps no state for it, so evaluate is fully stateless. |
input.transcript_text | array | The conversation so far, as { "speaker": "clinician" | "patient", "text": "..." }. Read by the deterministic emergency screen. No model reads it. |
input.facts | object | The extracted typed field values, as { field_id: value }. Deterministic protocol evaluation runs on these. |
input.protocol_id | string | Required when facts is sent. May be omitted with transcript_text; the engine detects the protocol. |
input.demographics | object | Optional. { "age": 47, "sex": "M" }. Feeds age- and sex-gated rules. |
Send at least one of transcript_text or facts. An empty input returns
400 invalid_request.
Response
{
"correlation_id": "consult-8f3a",
"coverage_state": "covered",
"active_protocol": { "id": "headache" },
"emergency": {
"urgent": true,
"findings": [
{ "id": "stroke_pattern", "provenance": "deterministic_emergency_canon" },
{ "id": "thunderclap_headache", "provenance": "deterministic_protocol" }
]
},
"protocol_result": {
"matched": true,
"rule_id": "red_flag_urgent_referral",
"diagnosis": { "label": "Urgent Referral Required", "disposition": "urgent" },
"derived_values": { "has_red_flags": true, "has_nausea_or_vomiting": true }
},
"disposition": "urgent",
"safety_status": "cannot_miss_positive",
"provenance": {
"deterministic": true,
"engine_release": "2026.08",
"audit_ref": "audit_f0812c423021"
}
}
| Field | What it means |
|---|---|
coverage_state | covered when a protocol applies to the conversation, else uncovered or none. |
active_protocol | The protocol the engine selected, or null. You did not choose it; the engine did. |
emergency | urgent plus the deterministic findings. Runs independent of protocol selection, so a cannot-miss emergency is caught even when no protocol matches. Each finding names its source. |
protocol_result | The rule that fired: matched, rule_id, diagnosis (with disposition), and the derived_values the rules computed. |
disposition | The engine's routing decision, e.g. urgent. |
safety_status | cannot_miss_positive, screening, or clear. Set by the engine; your model cannot set, clear, or delete it. |
provenance | deterministic is true whenever no model ran (the facts and transcript paths). Carries the engine release and an audit_ref you can quote when reporting an issue. |
cannot_miss, missing_fields, suggested_verifications | Coming. Three-valued red-flag states, the fields still worth extracting, and clinician prompts for unknown high-risk items. |
Determinism and safety
The shape is designed so the model cannot be the clinical authority, by construction:
| Guarantee | How |
|---|---|
| The model never decides | Red-flag states, emergency, and disposition come only from the engine. Your model fills in the blanks; it does not set the flags. |
| Emergencies are always screened | The emergency block runs even when no protocol is detected, so a heart attack or anaphylaxis is caught regardless of routing. |
| Deterministic and replayable | No model in the path. The same facts and transcript give the same result, and provenance.deterministic proves it. |
| Stateless and idempotent | Send the full current state each call. The server stores nothing per correlation_id; retries are safe. |
Extracting against typed fields (the facts path) needs the per-protocol field schema, which
tells your model exactly what to look for. A machine-readable schema endpoint is coming; until then, the
transcript_text path gives you the full emergency screen and protocol result with no schema
required. Reply to your onboarding contact with an audit_ref if any result looks wrong.
Configure the engine per request.
Set engine behavior with a config object on an intake, a batch scenario, or an evaluate call.
Pass a profile to apply a named bundle, and a config object to override
individual flags on top. Every result echoes the resolved configuration and a
config_hash, so two runs are comparable exactly when their hashes match.
Read the catalog
GET /v1/config lists the profiles and flags available to your key. Each flag gives its
allowed values and default.
$ curl https://api.auddax.ai/v1/config \
-H "Authorization: Bearer $AUDDAX_API_KEY"
{
"catalog_version": "2026-09-04",
"profiles": [ { "id": "default", "description": "Engine defaults." } ],
"flags": [
{ "key": "locale", "values": ["en-US", "es-AR", "pt-BR"], "default": "en-US",
"description": "Language of the patient-facing dialogue." }
]
}
Apply configuration
Send config when you create an intake. The response echoes the resolved config and its hash.
The same config applies to every turn in that encounter.
$ curl -X POST https://api.auddax.ai/v1/intakes \
-H "Authorization: Bearer $AUDDAX_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "config": { "locale": "es-AR" } }'
{
"encounter_id": "enc_...",
"status": "open",
"config": { "locale": "es-AR" },
"config_hash": "722a56cd1465f74c"
}
Batch scenarios take their own config, so one batch runs the same scenario under several
configurations for a side-by-side comparison. The evaluate call takes config and echoes it in
provenance. Both the Python and TypeScript SDKs expose the catalog through
client.config() and take config on the same calls.
Available flags
| Flag | Values | Controls |
|---|---|---|
locale | en-US, es-AR, pt-BR | The language of the patient dialogue. Default en-US. |
More flags are on the way, including the model that drives the encounter, the clinician-handoff language,
the interview cadence, and the handoff format. Each one appears in GET /v1/config as it goes
live, so your integration reads the current catalog rather than a fixed list.
400 invalid_request.
A delivered turn is never re-driven.
Turns are long calls, so client timeouts happen. A naive retry would drive a second clinical turn with the
same message. The Idempotency-Key header prevents that.
| Call | Safe to retry | Rule |
|---|---|---|
POST /v1/intakes |
Yes | A duplicate creates a second empty intake. Use the newest encounter_id. |
Blocking turn with Idempotency-Key |
Yes | A repeated key returns the stored response and consumes no quota. The replay carries the header x-auddax-idempotent-replay: true. |
| Blocking turn without a key | No | A retry drives a second turn. Always send the header. |
| Streaming turn | No | Idempotency does not apply to streams. If a stream drops, read the handoff first. |
GET requests |
Yes | All reads are safe to repeat. |
Choose one key per logical turn, for example turn-3-enc_abc. Replayed responses stay available
for about 24 hours.
Stable codes. Clear next steps.
Every error uses one envelope. Branch on error.code, not on the message text.
{
"error": {
"code": "intake_closed",
"message": "The intake reached a terminal state and takes no more turns.",
"encounter_id": "enc_auddax-sandbox_91a81eb05e4c58fa"
}
}
| HTTP | Code | Meaning | What to do |
|---|---|---|---|
| 400 | invalid_request | Bad JSON, a missing field, or an out-of-range value. | Fix the request. Do not retry as-is. |
| 401 | unauthorized | The key is missing, unknown, or revoked. | Check the header and the key. |
| 404 | not_found | The intake does not exist for this key. | Check the encounter_id. |
| 409 | intake_closed | The intake reached a terminal state. | Stop sending turns. Read the handoff. |
| 410 | intake_expired | The session passed the two-hour limit. | Create a new intake. The handoff stays readable. |
| 409 | batch_running | A batch is already running for this key. | Wait for it, or poll its status. |
| 429 | rate_limited | Too many requests this minute. | Wait for Retry-After seconds. |
| 429 | quota_exceeded | The daily turn quota is used up. | Resume tomorrow, or ask us to raise the quota. |
| 502 | upstream_error | The engine failed on this call. | Retry once with the same Idempotency-Key. |
| 503 | unavailable | The engine is unreachable. | Retry with backoff. |
| 500 | internal_error | A gateway fault. | Retry once. Report it if it repeats. |
Sandbox limits are per key.
| Limit | Default | On breach |
|---|---|---|
| Requests per minute | 6 | 429 rate_limited with Retry-After |
| Turns per day | 100 | 429 quota_exceeded |
| Intakes per day | 100 | 429 quota_exceeded |
Every turn runs real clinical inference, so the sandbox caps spend per key. The daily window resets at 00:00 UTC. Need more for a serious evaluation? Email team@auddax.ai and we raise your quota.
Check where you stand at any time. Every turn response also carries an
x-auddax-turns-remaining header, so your client can watch the budget without an extra call.
{
"date": "2026-08-21",
"turns": { "used": 12, "quota": 100, "remaining": 88 },
"intakes": { "used": 3, "quota": 100, "remaining": 97 },
"rpm_limit": 6
}
The clinician-ready result.
The handoff is the structured output of the intake. It arrives inside a terminal turn and from
GET /v1/intakes/{encounter_id}/handoff at any time.
{
"encounter_id": "enc_auddax-sandbox_91a81eb05e4c58fa",
"captured_at": "2026-08-21T16:32:41.902Z",
"terminal": true,
"urgent": true,
"intake_status": "complete",
"safety_status": "cannot_miss_positive",
"safety_notes": ["Cannot-miss pathway triggered."],
"disposition": "urgent_escalation",
"disposition_label": "Emergency evaluation",
"soap": {
"subjective": "58-year-old male with chief complaint of chest pressure...",
"objective": "No examination performed. Intake interview only.",
"assessment": "Presentation concerning for acute coronary syndrome...",
"plan": "Immediate emergency evaluation. 911 activation advised..."
},
"captured_facts": { "age": "58", "sex": "male", "pregnancy": null },
"active_protocol": "CERT-ACS-061",
"candidate_protocols": ["CERT-ACS-061", "CERT-CARD-060"],
"scores": [
{ "key": "rfs", "total": "6/6", "band": "strong", "components": "2/2/2" }
],
"conversation": [
{ "role": "patient", "text": "Chest pressure for the last hour..." },
{ "role": "assistant", "text": "I need you to call 911 right now..." }
],
"provenance": {
"release": "release_staging_runtime_v1",
"server_model": "claude-sonnet-4-5",
"prompt_hash": "sha256:1f8c...",
"source_commit": "b9c502d1...",
"directory_hash": "sha256:77aa...",
"runtime_profile": "pure_llm_studio_v1",
"prompt_profile": "protocolos_studio_may11_v1",
"voice_profile": "warm_concise_clinician_led_v1",
"audit_ref": "req_a7393c1094d7",
"latency_ms": 21408,
"protocol_count": 151
}
}
| Field | Meaning |
|---|---|
terminal · urgent | The intake's end state. Urgent means immediate clinician review. |
intake_status | What the intake still needs, or complete. |
safety_status · safety_notes | The deterministic safety state and its notes. |
disposition · disposition_label | The engine's disposition and its display label. |
soap | The clinician summary: subjective, objective, assessment, plan. |
captured_facts | Structured demographics captured during the intake. |
active_protocol · candidate_protocols | The committed protocol and the differential considered. |
scores | Quality scores. Populated on terminal intakes. Bands are strong, mid, weak. |
conversation | The patient-facing dialogue. Clinician-only content never appears here. |
provenance | The attested run identity. See provenance. |
Attested, not asserted.
Every handoff carries the identity of the run that produced it, reported by the engine itself. Pin these values in your evaluation records. Compare them across releases.
| Field | Meaning |
|---|---|
server_model | The model the engine actually ran. |
release · source_commit | The engine release and its source commit. |
prompt_hash · directory_hash | Content hashes of the prompt and the pinned protocol library. |
runtime_profile · prompt_profile · voice_profile | The pinned engine configuration for this tenant. |
audit_ref | The engine's audit reference for this run. Quote it in any support request. |
protocol_count | The size of the pinned protocol library. |
A research preview. Not for care.
Send no real patient data. Do not send personal health information, names, contact details, or record numbers. Use synthetic scenarios. Test messages persist in the engine's encounter store and inform engine evaluation.
The sandbox moves. It runs against the staging engine. Behavior can change and encounters can reset without notice. Provenance fields tell you exactly which release served each response.
Keys are personal. One key per developer or service. Do not share keys. We revoke on request, and revocation lands within one minute.
Support and incidents:
team@auddax.ai.
Include the encounter_id and the audit_ref when you have them.
Machine-readable, versioned.
The OpenAPI 3.1 spec is served by the API itself at /v1/openapi.yaml. Generate clients from it, or load it into your API tooling.
Every response carries the release in a header. GET /v1/health returns the same identity as
JSON.
$ curl -sI https://api.auddax.ai/v1/health | grep x-auddax-release
x-auddax-release: 2026.08+bd1ee4f
$ curl -s https://api.auddax.ai/v1/health
{"ok":true,"release":"2026.08","build_sha":"bd1ee4f0f2d33c36db815e607cb51ee26a53df44"}
Typed SDKs and webhooks are planned and not yet available. A reference client is available on request. For anything else, email team@auddax.ai.