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.

Base URL sandbox
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 outcome is no longer continue; terminal is then true.

Evaluating the engine with an AI agent? Start at Testing with an agent. The agent guide is plain text at api.auddax.ai/llms.txt.

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.

request
$ curl -X POST https://api.auddax.ai/v1/intakes \
  -H "Authorization: Bearer $AUDDAX_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "label": "quickstart",
    "demographics": { "age_years": 58, "sex": "male" },
    "patient_history": "Allergies: penicillin. Current medications: lisinopril."
  }'
response201 Created
{
  "encounter_id": "enc_auddax-sandbox_91a81eb05e4c58fa",
  "status": "open",
  "label": "quickstart",
  "metadata": {},
  "mode": "standard",
  "created_at": "2026-11-03T16:31:04.118Z",
  "expires_at": "2026-11-03T18:31:04.000Z",
  "config": { "locale": "en-US" },
  "config_hash": "6c0570a2ff7e1192"
}
Step 2 · Send a turn

Send each patient message as one turn. In curl, set ENCOUNTER_ID to the encounter_id from step 1. 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.

request
$ 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."
  }'
response200 OK
{
  "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,
  "outcome": "urgent",
  "terminal_reason": null,
  "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, outcome is continue, terminal stays false, and choices can carry quick replies you can render as buttons. The interview closes only when the patient says it has nothing more to add, for example “That's all. I have nothing more to add.” There is no turn cap.

A turn takes 10 to 40 seconds, and a closing turn can take longer. See latency for timeouts, and idempotency and retries before you retry a turn.

Step 3 · Read the handoff

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.

request
$ curl https://api.auddax.ai/v1/intakes/$ENCOUNTER_ID/handoff \
  -H "Authorization: Bearer $AUDDAX_API_KEY"

Read disposition for triage and clinical_assessment.working_diagnosis for the diagnosis. Which field to read has the details. The full handoff shape is documented in the handoff object.

Let an agent play the patient.

You can evaluate the engine with an AI agent. The agent plays a synthetic patient, sends one message per turn, and judges what the engine asks and how it closes. Point the agent at two machine-readable sources. They describe the same contract as these docs.

You play the patient. The engine plays the clinician. It decides the questions, the red-flag screens, and the close. The agent answers only from its scenario and says “I don't know” to anything the scenario does not cover. It never coaches the engine. Use synthetic patients only, never real patient data.
The patient must close the interview. The engine closes an interview only on a done signal: the patient says it has nothing more to add, for example “That's all. I have nothing more to add.” This lane has no turn cap. “I don't know” is not a done signal, and repeated non-answers are not a done signal. After the first done signal, the engine can ask one more question when a cannot-miss safety item is still missing. The patient answers it and says the done signal again. An emergency and a patient under 18 close without one.

The evaluation loop

StepCallWhat to do
1 · Create POST /v1/intakes Send a label (for example the scenario id) and optional metadata (for example the arm and the run). Unknown fields return 400 invalid_request, so a typo fails loudly.
2 · Turn POST /v1/intakes/{encounter_id}/turns Send one patient message with a new Idempotency-Key. Read assistant_message, choices, choice_mode, and outcome.
3 · Stop None Repeat step 2 while outcome is continue. When the scenario runs out, the patient says it has nothing more to add. Stop on complete, urgent, or not_eligible. Set your own turn limit too, so a run that never closes still ends.
4 · Result GET /v1/intakes/{encounter_id}/result One flat object to assert on: outcome, disposition, safety status, protocol, red flags, clinical assessment, and provenance. /handoff returns the full handoff and transcript.
5 · List GET /v1/intakes?label=… Collect your runs, newest first, with outcome and turn count. Free.
python · one scenario
import os, time, uuid, httpx

http = httpx.Client(
    base_url="https://api.auddax.ai",
    headers={"Authorization": f"Bearer {os.environ['AUDDAX_API_KEY']}"},
    timeout=240,                                   # a turn takes 10 to 40 s
)
RETRY = {"turn_in_progress", "upstream_error", "unavailable", "rate_limited"}
# Not retried: 504 upstream_timeout. The engine may still answer; see "Idempotency and retries".

def error_code(r):
    try:
        return r.json()["error"]["code"]
    except (ValueError, KeyError, TypeError):      # a proxy 502 can have a body that is not JSON
        return "upstream_error" if r.status_code == 502 else None

def turn(encounter_id, message):
    key = str(uuid.uuid4())                        # one key per patient message
    for _ in range(8):
        try:
            r = http.post(f"/v1/intakes/{encounter_id}/turns",
                          headers={"Idempotency-Key": key}, json={"message": message})
        except httpx.TransportError:
            time.sleep(10)
            continue                               # same key: never a second message
        if r.status_code == 200:
            return r.json()
        if error_code(r) not in RETRY:
            r.raise_for_status()
        time.sleep(int(r.headers.get("Retry-After", "10")))
    raise RuntimeError("the turn did not complete")

intake = http.post("/v1/intakes", json={
    "label": "sore-throat-01",
    "metadata": {"arm": "default", "run": "1"},
    "demographics": {"age_years": 34, "sex": "female"},
}).json()
eid = intake["encounter_id"]

reply = turn(eid, "I have had a sore throat for two days.")
while reply["outcome"] == "continue" and reply["turn_index"] < 12:
    # Your agent answers as the patient. When the scenario runs out, it says
    # "That's all. I have nothing more to add." The engine closes only on that.
    reply = turn(eid, play_patient(reply))

result = http.get(f"/v1/intakes/{eid}/result").json()
print(result["outcome"], result["disposition"], result["clinical_assessment"], result["provenance"]["source_commit"])

Retry rules

A retry must never send a patient message twice. The Idempotency-Key is claimed before the turn runs, and one turn per intake produces its reply at a time.

WhenDo this
Client timeout, network error, or a 502 from the proxyRetry the same request with the same Idempotency-Key. You get the stored result, or 409 turn_in_progress while the first request still runs.
A dropped streamA stream has no key. Read GET /v1/intakes/{encounter_id} before you send anything.
409 turn_in_progressWait Retry-After seconds, then retry with the same key. Retries count against the per-minute limit, so poll the free GET /v1/intakes/{encounter_id} for turn_in_progress and last_turn while you wait. Without a key, this code means another turn on the intake is still producing its reply and nothing was sent.
504 upstream_timeoutThe turn may still finish. Do not retry the key. Wait until GET /v1/intakes/{encounter_id} shows turn_in_progress: false, then read the handoff conversation: if your message is there, continue from the engine's reply; if not, send it with a new key. Note the encounter_id in your findings.
409 turn_outcome_unknownYou retried a key whose request timed out. Do the same as for 504.
502 upstream_error · 503 unavailableRetry later with the same key. A failed turn frees its key.
429 rate_limitedWait Retry-After seconds.
429 quota_exceededStop. The daily quota resets at 00:00 UTC, and Retry-After points there.

Timeouts. A turn takes 10 to 40 seconds, and a closing turn can take longer. Set the agent's HTTP timeout to at least 300 seconds. Many agent tool runners default to 30 or 60 seconds, which cuts turns off mid-reply. See latency for what happens at 120 seconds.

Report findings with the build. Quote engine.wrapper_build_sha from GET /v1/health and provenance.source_commit from the result, plus the encounter_id and audit_ref. The defaults the engine runs with are described in Engine behavior. For fixed scripts that do not adapt to the engine's questions, batches run server side.

Prefer to test from a chat? An Auddax MCP server runs the same loop from Claude Desktop or Claude Code. Your Auddax contact sends it to you with your key.

Typed clients for Python and TypeScript.

Version 0.5.0 of the official SDKs covers the standard intake 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. For routes outside the standard intake surface, call the HTTP API directly; the endpoint list and the spec name every route.

install
$ 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.

request
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)

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_.

header
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. Accepts a label and metadata to group runs.
  • GET /v1/intakes List your intakes, newest first. Filter by label and status. Free.
  • POST /v1/intakes/{encounter_id}/turns Send one patient message. Blocking or streaming.
  • GET /v1/intakes/{encounter_id} Status, outcome, turn_in_progress, and last_turn. Free.
  • GET /v1/intakes/{encounter_id}/result Compact result for assertions: outcome, routing, red flags, provenance.
  • GET /v1/intakes/{encounter_id}/handoff Clinician handoff and transcript. Works after close and expiry.
  • POST /v1/evaluate Beta. Evaluate a conversation you did not drive. Uses no turn quota. With no mode, this is the older deterministic path, not the consult engine.
  • POST /v1/transcripts/assess Research read of a finished transcript. Off by default. Not for clinical use. See Transcript read.
  • 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/config The configuration catalog: enabled modes, profiles, and flags. Free.
  • GET /v1/usage Today's usage against your quotas. Free.
  • GET /v1/usage/history Daily usage for up to 90 days. Free.
  • GET /v1/key The calling key's metadata and limits. Free.
  • GET /v1/health Liveness, release, and engine build. No auth.
  • GET /v1/openapi.json The machine-readable spec. Also /v1/openapi.yaml. No auth.
  • GET /llms.txt A one-page guide for AI agents. No auth.

All request and response bodies are JSON in snake_case. The default request body limit is 64 KB. File uploads accept up to 14 MB (a base64 file of at most 10 MiB), and POST /v1/evaluate and POST /v1/transcripts/assess accept up to 1 MB. Every response carries an x-auddax-release header with the release tag and build.

Mode-specific routes (file uploads, autonomy reviews, and guided turns) are described in Engine functions and in the spec.

Create an intake · request fields

FieldTypeRules
label string Optional. 1 to 120 characters. A free-text tag for grouping runs, for example a scenario id. Never sent to the engine. Filter with GET /v1/intakes?label=.
metadata object Optional. Up to 20 string pairs. Keys are 1 to 40 letters, digits, or _ . : -. Values are strings of at most 200 characters. Never sent to the engine.
demographics.age_years integer Optional. 0 to 130. A patient under 18 is not eligible; see Engine behavior.
demographics.sex string Optional. One of female, male, intersex.
patient_history string Optional. Up to 16,000 characters after trim. Include the source, date, and review status of history entries.
mode · profile · config · session_settings various Optional. See Configuration. A mode that is not enabled returns 400 mode_unavailable.

Every field is optional. Unknown fields are refused with 400 invalid_request, so a misspelled field fails loudly instead of being ignored.

Send a turn · request fields

FieldTypeRules
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.

The defaults mirror Certuma Link US production.

The sandbox engine runs with the same behavior defaults as Certuma Link US production. The engine owns this behavior, and it can change with the engine build. GET /v1/health names the engine lane and its build in engine.lane and engine.wrapper_build_sha. Quote the build with any finding.

DefaultWhat you see
One question per turnEach assistant_message asks one question.
Multi-select red-flag screensSome turns list several warning signs at once, with choice_mode: "multiple". Answer every choice that applies in one message, or say none apply.
Close on a done signalThe engine closes an interview only when the patient says it has nothing more to add. This lane has no turn cap. See Testing with an agent.
Emergency closeAn emergency closes the intake at once, with advice to call emergency services. outcome is urgent.
Self-harm decline accepted onceIf the patient declines the self-harm question, the engine accepts the decline and does not ask again.
Under 18 is not eligibleThe intake closes with outcome: "not_eligible", terminal_reason: "not_eligible_under_18", and no clinician handoff.
Split intakeThe patient interview and the clinician assessment are separate steps. The working diagnosis, differentials, and management options are written for the clinician, in the handoff's clinical_assessment.
US resourcesPatient-facing text names US resources: 911 for emergencies and 988 for a mental health crisis. config.locale es-AR and pt-BR change the language, not those resources.

Four outcomes

Every turn, intake read, result, handoff, and batch turn carries a normalized outcome. Branch on it instead of combining terminal and urgent yourself.

OutcomeTerminalMeaningNext
continueNoThe intake is still asking questions.Send the next patient message.
completeYesThe intake closed with a clinician handoff.Read the result or the handoff.
urgentYesThe intake closed with an urgent escalation.Surface it at once. Read the handoff.
not_eligibleYesThe intake closed without a handoff, for example a patient under 18. terminal_reason says why.Stop. Start a new intake for a different patient.

On the intake read and list, outcome is null before the first turn.

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.

open turns accepted POST turns closed turns return 409 expired turns return 410 terminal turn 2 h session limit handoff readable
The handoff endpoint answers in every state. Only open intakes accept turns.
StatusMeaningTurnsHandoff
openThe intake accepts turns.AcceptedReadable
closedThe engine reached a terminal state.409 intake_closedReadable
expiredThe session passed the two-hour limit.410 intake_expiredReadable

Check the state at any time with GET /v1/intakes/{encounter_id}. It is free and never calls the engine. The summary includes the label and metadata, the outcome, the turn count, the expiry time, and the last known safety status and disposition. It also carries turn_in_progress, true while a turn on this intake is producing its reply, and last_turn, the last reply with its turn_index, assistant_message, choices, and outcome. Read it to recover after a client timeout instead of resending.

GET /v1/intakes lists your intakes, newest first, with the same summary fields except last_turn. Filter with label and status (open, closed, expired). limit takes 1 to 200 (default 50). To page, pass before set to the previous page's next_before.

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.

FieldTypeMeaning
encounter_idstringThe intake this turn belongs to.
turn_indexintegerThe position of this turn, starting at 1.
assistant_messagestringThe next patient-facing message. Relay it without edits.
choicesstring[]The engine answer choices. Preserve the full list.
choice_modestringIf multiple, the turn is a multi-select screen: collect all selected answers and send them together in one message. If absent, use the existing single-answer behavior.
outcomestringcontinue, complete, urgent, or not_eligible. Keep sending turns only while it is continue. See Engine behavior.
terminal_reasonstring | nullWhy a terminal intake closed without a handoff, for example not_eligible_under_18. Null otherwise.
should_continue_intakebooleanThe engine's own statement of whether the intake can continue, when it sends one. False closes the intake.
terminalbooleanTrue when the intake reached a terminal state. Stop sending turns.
urgentbooleanTrue when the engine requires immediate clinician review. Surface it prominently.
safety_statusstring | nullThe deterministic safety state. Values include no_red_flags_yet, cannot_miss_negative, cannot_miss_positive.
dispositionstring | nullThe engine's disposition. Values include continue_intake, clinician_review, self_care, urgent_escalation.
disposition_labelstring | nullA display label for the disposition.
handoffobjectPresent when terminal is true. The full handoff object.
modestringThe engine mode the intake runs in. See Engine functions.
Treat enumerations as open sets. New safety statuses and dispositions can appear as the engine's coverage grows. Branch on the values you know. Pass unknown values through to your clinician surface.

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 10 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.

request
$ 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 }'

Events

EventDataMeaning
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.completedturn objectThe 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.

POST /v1/intakes/{encounter_id}/turns · stream

Recorded sandbox events. Timing compressed for the demo.

You do not have to wait for the tail. When 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.

message.delta text can differ from the final reply: the engine can remove a sentence after it was streamed. The assistant_message in turn.completed is the authoritative text. Replace the streamed text with it.

If a stream drops, do not resend the message blindly. The turn may have been delivered. Read GET /v1/intakes/{encounter_id}: turn_in_progress and last_turn tell you whether the message landed. 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.

MilestoneTypical, warmWhat to show
First message.deltaA few secondsThe reply, appearing as it streams.
Complete patient message10 to 40 secondsThe full reply. The patient can answer now.
Snapshot and handoffShortly afterNothing. It compiles in the background.

Set your client timeout to at least 300 seconds. A blocking closing turn can take longer than the table says, because the engine writes the note before it answers. The host api.auddax.ai runs through a proxy. When a response has not started in 120 seconds, the proxy returns its own 502, and that body can be something other than the JSON error envelope, so read the status before you parse it. The turn can still finish. The gateway behind the proxy holds a turn open for up to 285 seconds; if the engine has not answered by then, the gateway returns 504 upstream_timeout. If the proxy or your own client gives up first, retry with the same Idempotency-Key (see idempotency and retries). Use streaming for every patient-facing surface: text arrives within seconds, so a stream normally stays inside the proxy limit. 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.

register
$ 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" }'
response201 Created
{
  "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

EventFires when
intake.completedAn intake reaches a terminal state without an urgent escalation.
intake.escalatedAn intake ends with an urgent escalation. Treat it with priority.
batch.completedA 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.

node
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.

create
$ 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" } }
    ]
  }'
response202 Accepted
{ "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.

Each scenario's turns pairs every patient_message with the engine's assistant_message and the turn's outcome, so you can score a transcript without another call. Each scenario's intake carries the scenario label (or the batch label) and the metadata batch_id and scenario_index, so GET /v1/intakes?label= finds it.

GET /v1/batches/{batch_id} · one scenario, abridged200 OK
{
  "index": 1,
  "status": "completed",
  "label": "urgent",
  "encounter_id": "enc_auddax-sandbox_5be0a7c19d2f4e83",
  "turns": [
    {
      "turn_index": 1,
      "patient_message": "Chest pressure for an hour, sweating.",
      "assistant_message": "I need you to call 911 right now. [...]",
      "terminal": true,
      "urgent": true,
      "outcome": "urgent",
      "safety_status": "cannot_miss_positive",
      "disposition": "urgent_escalation"
    }
  ]
}

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.

Select the engine mode

Read GET /v1/config for engine.modes. The operator configures the available connections. Existing requests use standard mode. A selected mode stays with the intake for every turn and handoff.

ModeFunctions
standardThe existing clinical intake and handoff.
configuredSession settings for interview policies, prompt profile, clinician locale, and session length. Upload analysis uses the same intake.
autonomousThe engine autonomy result, reasons, condition, and plan. The current engine contract uses en-US.

Set mode when creating an intake. Supply session_settings only in configured mode. See Configuration for the accepted values and a request example. Unknown settings are refused.

Send a file to POST /v1/intakes/{encounter_id}/uploads as content_base64. Use PDF, JPEG, PNG, or WebP, at most 10 MiB. Check upload.status and the verification fields. The next turn reads the analysis. One analysis attempt uses one daily turn quota unit.

An autonomous handoff can contain autonomy. Use the engine's outcome, reasons, and plan. The model field is the proposal before the gate. The autonomy catalog is separate from the protocol reference corpus.

Read synthetic cases from GET /v1/autonomy/scenarios. Read or add reviews at /v1/intakes/{encounter_id}/autonomy/reviews. Review access requires the intake owner's API key. Uploads and review writes do not support automatic retries after a network or engine failure.

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.

This is not the consult engine. POST /v1/evaluate with no mode is a separate older deterministic path. It asks no questions, runs no model, and returns no clinical_assessment. Do not use its result to judge the interview. To test the interview, use the intake endpoints.

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.

Beta. The evaluate lane is in active development. The request and response shapes below are stable; fields marked coming populate in later phases. Send synthetic data only, as with the rest of the sandbox.

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.

1. Your model reads the transcript so far and extracts the protocol's typed fields (or you pass the raw transcript text).
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.

request
$ 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." }
      ]
    }
  }'

Request fields

FieldTypeRules
correlation_idstringOptional. For your own audit correlation only. The server keeps no state for it, so evaluate is fully stateless.
input.transcript_textarrayThe conversation so far, as { "speaker": "clinician" | "patient", "text": "..." }. Read by the deterministic emergency screen. No model reads it.
input.factsobjectThe extracted typed field values, as { field_id: value }. Deterministic protocol evaluation runs on these.
input.protocol_idstringRequired when facts is sent. May be omitted with transcript_text; the engine detects the protocol.
input.demographicsobjectOptional. { "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

response200 OK
{
  "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.11",
    "audit_ref": "audit_f0812c423021"
  }
}
FieldWhat it means
coverage_statecovered when a protocol applies to the conversation, else uncovered or none.
active_protocolThe protocol the engine selected, or null. You did not choose it; the engine did.
emergencyurgent 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_resultThe rule that fired: matched, rule_id, diagnosis (with disposition), and the derived_values the rules computed.
dispositionThe engine's routing decision, e.g. urgent.
safety_statuscannot_miss_positive, screening, or clear. Set by the engine; your model cannot set, clear, or delete it.
provenancedeterministic 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_verificationsComing. 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:

GuaranteeHow
The model never decidesRed-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 screenedThe emergency block runs even when no protocol is detected, so a heart attack or anaphylaxis is caught regardless of routing.
Deterministic and replayableNo model in the path. The same facts and transcript give the same result, and provenance.deterministic proves it.
Stateless and idempotentSend the full current state each call. The server stores nothing per correlation_id; retries are safe.
Degradation path (required). If evaluate fails or times out, show “analysis pending.” Never show a false all-clear, and never compute a disposition or a gate on your side. The authority is the engine's response, or nothing.

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.

Read a finished transcript in one pass.

A research read. Not for clinical use. POST /v1/transcripts/assess forces a diagnosis, which the live product does not force. It asks no questions, and it is not an intake. It is off by default: your Auddax contact enables it per key or per account. Any other key gets 503 unavailable, and a retry does not change that. The sandbox terms apply.
request
$ curl -X POST $AUDDAX_DIRECT_BASE_URL/v1/transcripts/assess \
  -H "Authorization: Bearer $AUDDAX_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: chat-0001" \
  --max-time 330 \
  -d '{
    "messages": [
      { "role": "patient", "content": "I have had a sore throat for two days." },
      { "role": "clinician", "content": "Do you have a fever?" },
      { "role": "patient", "content": "A low one, last night." }
    ],
    "locale": "en-US",
    "label": "run-1"
  }'
response · abridged200 OK
{
  "status": "final",
  "not_eligible_reason": null,
  "disposition": "clinician_review",
  "disposition_label": "[...]",
  "safety_status": "[...]",
  "requires_immediate_clinician_review": false,
  "clinical_assessment": {
    "working_diagnosis": "[...]",
    "differentials": ["[...]"],
    "management": [{ "kind": "treatment_option", "text": "[...]" }]
  },
  "clinician_note": "[...]",
  "active_protocol": "[...]",
  "grounding": { "status": "[...]", "reason": null },
  "dialogue_error": null,
  "research_read": true,
  "provenance": { "source_commit": "[...]", "prompt_profile": "[...]", "server_model": "[...]", "note_effort": "[...]" }
}
RuleWhat it means
Cut the transcriptYou must cut each transcript before the first clinician message that gives a diagnosis or a plan, because the route cannot clean a transcript. A message after that point reaches the engine as input.
No dosesTreatment options carry no doses, by design, and the clinician decides. Nothing in management is decided.
Under 18A chat judged to be about a person under 18 returns status: "not_eligible" with no assessment.
FieldsRead clinical_assessment.working_diagnosis for the diagnosis and disposition for triage. research_read is always true. The gateway returns the engine's response unchanged.
LimitsA 1 MiB body, 1 to 600 messages with at least one from the patient, and 120,000 transcript characters. role is patient or clinician. locale is en-US, es-AR, or pt-BR. label is at most 120 characters. Unknown fields are refused.
CostOne request of the per-minute limit and one unit of the daily turn quota. A request that the gateway refuses costs no quota.
TimeoutThe gateway waits up to 300 seconds for the engine, then returns 504 upstream_timeout. Set your client timeout to at least 330 seconds.
Base URLThe proxy at api.auddax.ai returns its own 502 when a response has not started in 120 seconds, and a read can take longer. Call this route on the direct base URL your Auddax contact gives you.
No replayThe gateway stores no transcript text and no output, and it logs counts only. It keeps a hash of the request, so it cannot return a result twice. A required Idempotency-Key of up to 128 letters, digits, or _ . : - guards each run.
RetriesA used key gets 409 operation_conflict, also while the first request still runs. After a lost connection, 502, 503, or 504, the run may have happened and the result is gone: send a new key. That is a new model pass and a new quota unit. A key stays free after 429 and after a connection that could not open.

Select the mode and session settings.

Set mode when you create an intake. Use config.locale for the patient dialogue language. Use session_settings in configured mode for interview policies, the prompt profile, the clinician language, and the session length. These settings stay with the intake.

Read the catalog

GET /v1/config returns two groups. flags lists the values accepted in config. This group currently contains only locale. engine.modes lists the enabled connections. When configured mode is enabled, engine.session_settings lists its accepted settings.

request
$ curl https://api.auddax.ai/v1/config \
  -H "Authorization: Bearer $AUDDAX_API_KEY"

Example response with all three connections enabled. Use the modes returned by your API deployment. If engine is absent, that deployment does not expose this connection catalog.

example response200 OK
{
  "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." }
  ],
  "engine": {
    "contract_version": "engine_connections_v1",
    "modes": ["standard", "configured", "autonomous"],
    "session_settings": {
      "policies": {
        "interview_pacing_policy": "interview_pacing_v1",
        "question_budget_policy": "single_question_v1",
        "answer_choices_policy": "multi_select_screen_v1"
      },
      "prompt_profiles": ["protocolos_studio_may11_v1", "protocolos_studio_plus10_v2"],
      "clinician_locales": ["en-US", "es-AR", "pt-BR"],
      "session_ttl_minutes": { "min": 5, "max": 1440 }
    },
    "uploads": {
      "mode": "configured",
      "max_bytes": 10485760,
      "max_per_session": 10,
      "mime_types": ["application/pdf", "image/jpeg", "image/png", "image/webp"]
    }
  }
}

Set the patient language

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.

request
$ 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" } }'
response201 Created
{
  "encounter_id": "enc_...",
  "status": "open",
  "config": { "locale": "es-AR" },
  "config_hash": "722a56cd1465f74c"
}
Patient writes inconfig.localeClinician fields on auddax-sandbox
Englishen-US (the default)English
Spanishes-AREnglish
Portuguesept-BREnglish

Any other value is refused with 400 invalid_request, for example es-MX or es. The engine writes to the patient in the locale, not in the language the patient writes in, so with the default a patient who writes in Spanish gets replies in English. Set the locale when you create the intake. The clinician fields are soap, clinician_summary, and clinical_assessment. In pt-BR, the not-eligible close and the self-harm support line are fixed text in English.

An omitted mode uses standard. Each batch scenario also accepts config. Batches use standard mode and do not accept session_settings. Evaluate accepts config through its separate request contract. Intake modes and session_settings do not apply to evaluate.

Intake configuration fields

FieldValuesUse
modestandard, configured, autonomousSelect an enabled connection. Default standard. The current autonomous contract requires en-US.
config.localeen-US, es-AR, pt-BRThe language of the patient dialogue. Default en-US.
profiledefaultApply a named configuration bundle. Values in config override the bundle. This field is separate from the engine prompt profile.

Settings for configured mode

Put these fields inside session_settings. Each field is optional. Put the three policy fields inside session_settings.policies. Omitted settings keep the engine defaults.

FieldAccepted valueUse
policies.interview_pacing_policyinterview_pacing_v1Select the engine interview pacing policy.
policies.question_budget_policysingle_question_v1Select the policy for one question per turn.
policies.answer_choices_policymulti_select_screen_v1Allow questions with multiple answer selections. Follow the returned choice_mode.
prompt_profile_idprotocolos_studio_may11_v1, protocolos_studio_plus10_v2Select an engine prompt profile.
clinician_localeen-US, es-AR, pt-BRSet the clinician output language.
session_ttl_minutesInteger from 5 to 1440Set the session length in minutes.

Apply session settings

Use this request only when engine.modes includes configured. The create response returns mode, config, the supplied session_settings, and config_hash.

request
$ curl -X POST https://api.auddax.ai/v1/intakes \
  -H "Authorization: Bearer $AUDDAX_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "mode": "configured",
    "config": { "locale": "es-AR" },
    "session_settings": {
      "policies": {
        "interview_pacing_policy": "interview_pacing_v1",
        "question_budget_policy": "single_question_v1",
        "answer_choices_policy": "multi_select_screen_v1"
      },
      "prompt_profile_id": "protocolos_studio_plus10_v2",
      "clinician_locale": "es-AR",
      "session_ttl_minutes": 720
    }
  }'

Both SDKs read the catalog with client.config(). For intake creation, the Python SDK accepts mode and session_settings. The TypeScript SDK accepts mode and sessionSettings. The API uses session_settings.

The configuration hash identifies the requested settings. It does not identify the engine build or guarantee the same clinical result. Read the handoff provenance for the engine version. See Engine functions for uploads and autonomy.

The operator selects the enabled connections. Auddax preserves the engine access checks and clinical decisions. The API refuses unknown settings and values. It also refuses session_settings in standard or autonomous mode. A mode that is not enabled on this deployment returns 400 mode_unavailable, and its message lists the enabled modes.

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.

CallSafe to retryRule
POST /v1/intakes Yes A duplicate creates a second empty intake. Use the newest encounter_id.
Blocking turn with Idempotency-Key Yes The key is claimed before the turn runs. A repeated key returns the stored response and consumes no quota; the replay carries the header x-auddax-idempotent-replay: true. While the first request still runs, a retry gets 409 turn_in_progress with Retry-After. A turn that fails with any error except 504 frees its key, so the same key can retry it.
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 GET /v1/intakes/{encounter_id} first.
POST /v1/transcripts/assess No replay The route stores no result, so a used key gets 409 operation_conflict. A new key runs a new model pass. See Transcript read.
GET requests Yes All reads are safe to repeat.

One turn per intake at a time. A turn sent while another turn on the same intake is still producing its reply gets 409 turn_in_progress, and nothing is sent to the engine. With streaming, the intake accepts the next turn once message.completed arrives.

Recover after a client timeout or a 502 from the proxy. Retry the same request with the same key, or read GET /v1/intakes/{encounter_id}. It is free: turn_in_progress says whether a turn is still running, and last_turn holds the last reply. Poll it while you wait instead of resending; retries count against the per-minute limit.

504 upstream_timeout is ambiguous. The engine did not answer in time, and the turn may still complete. The gateway keeps the intake locked for a while, and it refuses that key from then on: a retry with it gets 409 turn_outcome_unknown, so the message can never reach the engine twice. Wait until GET /v1/intakes/{encounter_id} shows turn_in_progress: false, then read GET /v1/intakes/{encounter_id}/handoff. If your message is the last patient message in conversation, the engine has it and the reply follows it. If not, send the message again with a new key.

Choose one key per patient message, for example a UUID or turn-3-enc_abc. Never reuse a key for a different message, and never send a new key for a message you already sent. 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. 409 turn_in_progress, 429, and 504 carry a Retry-After header in seconds.

error envelope409 Conflict
{
  "error": {
    "code": "intake_closed",
    "message": "The intake reached a terminal state and takes no more turns.",
    "encounter_id": "enc_auddax-sandbox_91a81eb05e4c58fa"
  }
}
HTTPCodeMeaningWhat to do
400invalid_requestBad JSON, a missing or unknown field, an out-of-range value, or a body over the endpoint limit.Fix the request. The message names the field. Do not retry as-is.
400mode_unavailableThe requested mode is not enabled on this deployment.Use a mode from engine.modes in GET /v1/config, or omit mode for standard. Do not retry as-is.
401unauthorizedThe key is missing, unknown, or revoked.Check the header and the key.
404not_foundThe intake does not exist for this key, or the path is unknown.Check the encounter_id and the path.
409intake_closedThe intake reached a terminal state.Stop sending turns. Read the result or the handoff.
409turn_outcome_unknownAn earlier request with this Idempotency-Key timed out, and the engine may have the message.Do not retry the key. Read the handoff conversation; if your message is not there, send it with a new key.
409turn_in_progressA turn on this intake is still producing its reply, or a request with this Idempotency-Key is still running. Nothing new was sent to the engine.Wait Retry-After seconds. Poll GET /v1/intakes/{encounter_id} (free) for turn_in_progress and last_turn, then retry with the same key.
409operation_conflictThe request conflicts with stored state: a guided turn_id or an evaluate Idempotency-Key reused with different input, a used key on POST /v1/transcripts/assess, or an upload or review the session is not ready for.Reload the current state. Send new input with a new key or turn_id. Do not retry as-is.
409batch_runningA batch is already running for this key.Wait for it, or poll its status.
410intake_expiredThe session passed the two-hour limit.Create a new intake. The handoff stays readable.
429rate_limitedToo many requests this minute.Wait for Retry-After seconds.
429quota_exceededThe daily turn or intake quota is used up.Stop. The quota resets at 00:00 UTC; Retry-After counts the seconds until then. Ask us to raise the quota if you need more.
502upstream_errorThe engine failed on this call.Retry later with the same Idempotency-Key. A failed turn frees its key.
502NoneThe proxy at api.auddax.ai did not get a response in 120 seconds. This body can be something other than this envelope. The turn can still finish.Read the status before you parse the body. Retry a blocking turn with the same Idempotency-Key.
503unavailableThe engine is unreachable, or a feature is switched off.Retry with backoff, with the same Idempotency-Key. A switched-off feature says so in the message, and a retry does not change it.
504upstream_timeoutThe engine did not answer in time. The turn may still complete.Do not retry the key. Wait until turn_in_progress is false, then read the handoff conversation. If your message is not there, send it with a new key.
500internal_errorA gateway fault.Retry once. Report it if it repeats.

Sandbox limits are per key.

LimitDefaultOn breach
Requests per minute6429 rate_limited with Retry-After
Turns per day100429 quota_exceeded with Retry-After to 00:00 UTC
Intakes per day100429 quota_exceeded with Retry-After to 00:00 UTC

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.

Some reads are free: they do not count against the per-minute limit. They are GET /v1/intakes, GET /v1/intakes/{encounter_id}, GET /v1/usage, GET /v1/usage/history, GET /v1/config, GET /v1/key, and GET /v1/health. The result and handoff reads, creates, turns, and turn retries that get 409 turn_in_progress do count. A replayed turn is free. One POST /v1/transcripts/assess uses one request of the per-minute limit and one turn of the daily 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.

GET /v1/usage200 OK
{
  "date": "2026-11-03",
  "turns": { "used": 12, "quota": 100, "remaining": 88 },
  "intakes": { "used": 3, "quota": 100, "remaining": 97 },
  "evaluations": { "used": 0 },
  "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.

handoff · abridged real sandbox response200 OK
{
  "encounter_id": "enc_auddax-sandbox_91a81eb05e4c58fa",
  "captured_at": "2026-08-21T16:32:41.902Z",
  "terminal": true,
  "urgent": true,
  "outcome": "urgent",
  "terminal_reason": null,
  "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"],
  "red_flags": ["Chest pressure at rest"],
  "protocol_result": {
    "detected_protocol": "CERT-ACS-061",
    "coverage_state": "covered",
    "safety_triage": "urgent",
    "rule_id": "red_flag_urgent_referral",
    "cannot_miss_screen_incomplete": false
  },
  "clinician_summary": "58-year-old male with one hour of chest pressure at rest...",
  "clinical_assessment": null,
  "allergies": "Penicillin",
  "current_medication": "Lisinopril",
  "medication_identity": { "unverified": [], "verified": ["lisinopril"] },
  "references": null,
  "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
  }
}
FieldMeaning
terminal · urgentThe intake's end state. Urgent means immediate clinician review.
outcome · terminal_reasonThe normalized outcome (continue, complete, urgent, not_eligible) and, for a close without a handoff, the reason. See Engine behavior.
intake_statusWhat the intake still needs, or complete.
safety_status · safety_notesThe deterministic safety state and its notes.
disposition · disposition_labelThe engine's disposition and its display label.
soapThe clinician summary: subjective, objective, assessment, plan.
captured_factsStructured demographics captured during the intake.
active_protocol · candidate_protocolsThe committed protocol and the differential considered.
red_flagsThe red flags the engine recorded, in its own words.
protocol_resultThe protocol verdict: detected_protocol, coverage_state, safety_triage, rule_id, and cannot_miss_screen_incomplete. The last is true when the intake closed with a cannot-miss screen still pending: a review signal, never a routing input. Null when the engine sends none.
clinician_summaryThe engine's clinician handoff summary text. It can open with a medication identity notice.
clinical_assessmentThe split intake's working_diagnosis, differentials, and management options, for the clinician. Null when the engine did not write one, for example on an open intake, a not-eligible close, or a failed note. working_diagnosis can read “Undetermined” with the reason. Treatment options carry no doses, by design, and the clinician decides.
allergies · current_medicationWhat the intake captured, as text. Null when not captured.
medication_identityGeneric medicine names in the note that the patient did not supply (unverified) or that the reference table resolved (verified). A review signal, not identity verification.
referencesEvidence references for the protocols the engine used, when the engine supplies them. Otherwise null.
scoresQuality scores. Populated on terminal intakes. Bands are strong, mid, weak.
conversationThe patient-facing dialogue. Clinician-only content never appears here.
provenanceThe attested run identity. See provenance.

Which field to read

You wantReadNot this
Triagedisposition: clinician_review, self_care, urgent_escalation, or not_eligible, and continue_intake while the intake is open. disposition_label is its display text.outcome says how the intake ended. safety_status is the state of the cannot-miss screen. safety_triage is only urgent or routine.
Diagnosisclinical_assessment.working_diagnosis, with differentials and management. On the result, the handoff, and the handoff in a terminal turn.active_protocol and detected_protocol name a protocol. soap.assessment is prose for the note. POST /v1/evaluate is a different path.

The compact result

GET /v1/intakes/{encounter_id}/result returns one flat object a test harness can assert on. It reads the handoff once, so it counts against the per-minute limit.

GET /v1/intakes/{encounter_id}/result · abridged200 OK
{
  "encounter_id": "enc_auddax-sandbox_91a81eb05e4c58fa",
  "label": "chest-pain-01",
  "metadata": { "arm": "default", "run": "3" },
  "status": "closed",
  "turn_count": 1,
  "outcome": "urgent",
  "terminal_reason": null,
  "safety_status": "cannot_miss_positive",
  "disposition": "urgent_escalation",
  "active_protocol": "CERT-ACS-061",
  "safety_triage": "urgent",
  "cannot_miss_screen_incomplete": false,
  "red_flags": ["Chest pressure at rest"],
  "clinical_assessment": {
    "working_diagnosis": "[...]",
    "differentials": ["[...]"],
    "management": [{ "kind": "referral", "text": "[...]" }]
  },
  "medication_identity_unverified": [],
  "patient_turns": 1,
  "last_assistant_message": "I need you to call 911 right now...",
  "provenance": { "release": "release_staging_runtime_v1", "source_commit": "b9c502d1...", "audit_ref": "req_a7393c1094d7" }
}

It also carries mode, config_hash, terminal, urgent, disposition_label, candidate_protocols, coverage_state, safety_notes, and the provenance server_model, prompt_hash, and prompt_profile.

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.

FieldMeaning
server_modelThe model the engine actually ran.
release · source_commitThe engine release and its source commit.
prompt_hash · directory_hashContent hashes of the prompt and the pinned protocol library.
runtime_profile · prompt_profile · voice_profileThe pinned engine configuration for this tenant.
audit_refThe engine's audit reference for this run. Quote it in any support request.
protocol_countThe size of the pinned protocol library.

GET /v1/health adds the serving side: engine.lane names the engine lane behind standard mode, and engine.wrapper_build_sha names its build (null when unreadable). Record both with source_commit when you compare runs.

A research preview. Not for care.

Do not use the sandbox for real patient care. The sandbox exists for development and evaluation. It is not a medical device. Clinical decisions belong to clinicians.

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.json and /v1/openapi.yaml. Generate clients from it, or load it into your API tooling. AI agents can start from the one-page guide at /llms.txt. None of these need a key.

The current release is 2026.11. Every response carries the release in a header. GET /v1/health returns the same identity as JSON, plus the engine lane and build.

release identity200 OK
$ curl -sI https://api.auddax.ai/v1/health | grep x-auddax-release
x-auddax-release: 2026.11+bd1ee4f

$ curl -s https://api.auddax.ai/v1/health
{"ok":true,"release":"2026.11","build_sha":"bd1ee4f0f2d33c36db815e607cb51ee26a53df44",
 "engine":{"lane":"auddax-staging","wrapper_build_sha":"0c4e9a7d2b1f..."}}

Typed clients are described in SDKs and event delivery in webhooks. For anything else, email team@auddax.ai.