DUKTUS PRO API
Therapeutic AI for practice management software. GDPR-compliant, EU-hosted in Frankfurt.
What's available
| Service | Description | Price |
|---|---|---|
| Chat | Therapeutic AI with 5 modes — standard, diagnostics, planning, documentation, crisis | 8–20 ct |
| Voice | Audio-to-text transcription with optional PII anonymization. EU backend available. | 12–18 ct |
| Assessments | PHQ-9, GAD-7, PCL-5, ISI, AUDIT-C scoring with clinical references | Free |
| Anonymizer | GDPR PII detection and reversible anonymization | 3 ct |
| Evidence | ICD-10 disorder database, clinical guidelines, interventions | 0–10 ct |
| Generator | Clinical documents from 11 validated templates, PDF export | 20 ct |
| Text Improve | Convert shorthand notes into professional clinical language. Uses dkts_base_mini — smallest, fastest model | 6 ct |
Base URL: https://api.duktus-pro.de/api/v1
GDPR & Data Privacy
DUKTUS PRO is built for healthcare. Every design decision prioritizes patient data protection.
[PERSON_1], [DATUM_1], etc.).
The original values are restored in the response before it reaches your system.
| Measure | Detail |
|---|---|
| EU Infrastructure | All services run in AWS eu-central-1 (Frankfurt, Germany) |
| Voice — EU option | backend=private routes audio through our EU transcription service in Frankfurt. Audio never leaves the EU. |
| Reversible anonymization | Encrypted mappings are returned with every response — you can restore names at any time using /deanonymize |
| Art. 9 compliance | Designed for health-category data processing under GDPR Article 9 |
| No AI training on your data | Patient conversations are not used to train the underlying models |
| Audit trail | All API calls are logged per-partner with timestamps for compliance audits |
Authentication
All endpoints except GET /health require an API key in the request header.
X-API-Key: dk_live_YOUR_40_CHAR_KEY
Keys are issued by DUKTUS PRO. Format: dk_live_ followed by 40 characters.
Contact api@duktus-pro.de to request access.
First API Call — Health Check
No authentication required. Confirm the API is reachable and all services are operational.
curl https://api.duktus-pro.de/api/v1/health
import requests r = requests.get("https://api.duktus-pro.de/api/v1/health") print(r.json())
const r = await fetch('https://api.duktus-pro.de/api/v1/health'); const data = await r.json(); console.log(data.status); // "healthy"
{
"status": "healthy",
"api_version": "2.1.5",
"services": {
"anonymizer": { "status": "healthy" },
"evidence": { "status": "healthy" },
"generator": { "status": "healthy" }
}
}
Chat — Therapeutic AI
The primary endpoint. Send clinical context in plain text — PII anonymization is automatic. You send patient names, the AI never sees them.
Modes
| Mode | Use case | Price |
|---|---|---|
standard | General therapeutic questions | 15 ct |
diagnostik | ICD-10 differential diagnosis | 20 ct |
therapieplanung | Evidence-based treatment planning | 20 ct |
dokumentation | Session notes and clinical reports | 8 ct |
krisenintervention | Crisis assessment and safety planning | 20 ct |
curl -X POST https://api.duktus-pro.de/api/v1/chat \ -H "X-API-Key: dk_live_YOUR_KEY" \ -H "Content-Type: application/json" \ -d '{ "message": "Patient Maria Mueller, 34F, reports persistent insomnia for 3 weeks. PHQ-9: 12.", "mode": "diagnostik" }'
import requests API_KEY = "dk_live_YOUR_KEY" BASE = "https://api.duktus-pro.de/api/v1" headers = {"X-API-Key": API_KEY} r = requests.post(f"{BASE}/chat", headers=headers, json={ "message": "Patient Maria Mueller, 34F, persistent insomnia 3 weeks. PHQ-9: 12.", "mode": "diagnostik", }) data = r.json() print(data["response"]) # AI response -- names already restored print(data["usage"]["cost_cents"]) # 20 print(data["session_id"]) # reuse for follow-up messages
const r = await fetch('https://api.duktus-pro.de/api/v1/chat', { method: 'POST', headers: { 'X-API-Key': 'dk_live_YOUR_KEY', 'Content-Type': 'application/json' }, body: JSON.stringify({ message: 'Patient Maria Mueller, 34F, persistent insomnia 3 weeks.', mode: 'diagnostik', }), }); const { response, session_id, usage } = await r.json(); console.log(response); // AI response (names restored) console.log(usage.cost_cents); // 20
Multi-turn conversations
Pass session_id from the first response to maintain context across follow-up messages:
r1 = requests.post(f"{BASE}/chat", headers=headers, json={"message": "Patient Max Bauer, PHQ-9 score: 18.", "mode": "diagnostik"}) session_id = r1.json()["session_id"] r2 = requests.post(f"{BASE}/chat", headers=headers, json={"message": "What therapy approach would you recommend?", "session_id": session_id})
Voice — Audio Transcription
Transcribe session recordings to text. The private backend processes audio in Frankfurt (EU) — audio never leaves the EU. Enable anonymize=true to strip PII from the transcript.
Backends
| Backend | Speed (1h audio) | Data location | GDPR | Price |
|---|---|---|---|---|
fast (default) |
~30–60 s | Outside EU | Not suitable for patient data | 12 ct |
private |
~60–120 s | EU (Frankfurt) | GDPR-compliant | 18 ct |
Formats: WAV, MP3, WebM, M4A, FLAC, OGG (max 25 MB) · Languages: de, en, fr, es, it, pt, nl, pl, tr, ru, ja, ko, zh
# Fast (outside EU -- for non-patient data) curl -X POST https://api.duktus-pro.de/api/v1/voice/transcribe \ -H "X-API-Key: dk_live_YOUR_KEY" \ -F "audio=@recording.mp3" \ -F "language=de" \ -F "backend=fast" # GDPR-compliant (EU Frankfurt) + auto-anonymize curl -X POST https://api.duktus-pro.de/api/v1/voice/transcribe \ -H "X-API-Key: dk_live_YOUR_KEY" \ -F "audio=@recording.mp3" \ -F "language=de" \ -F "backend=private" \ -F "anonymize=true"
with open("recording.mp3", "rb") as f: r = requests.post( f"{BASE}/voice/transcribe", headers={"X-API-Key": API_KEY}, files={"audio": ("recording.mp3", f, "audio/mpeg")}, data={ "language": "de", "backend": "private", # EU/GDPR "anonymize": "true", # strip PII from transcript }, ) d = r.json()["data"] # d["transcript"] -> full text # d["duration_seconds"] -> audio length in seconds # d["anonymization"]["entities_found"] -> PII removed (if anonymize=true)
backend=private (Frankfurt, EU) and enable anonymize=true.
This ensures audio stays in the EU and PII is stripped before the transcript is stored.
Requires scope voice on your API key.Assessments
Clinical scoring tools — completely free. Validated against clinical standards.
Supported: phq9 (9 items) ·
gad7 (7 items) ·
pcl5 (20 items) ·
isi (7 items) ·
auditc (3 items) — all values 0–3 per item.
r = requests.post(f"{BASE}/assessments/score", headers=headers, json={ "tool": "phq9", "responses": [2, 3, 2, 1, 2, 3, 2, 1, 1], # 9 items, 0-3 each }) d = r.json()["data"] # d["score"] -> 17 # d["severity"] -> "Moderate Depression" # d["reference"] -> "Loewe et al. 2002"
"PHQ-9: 17/27 (Moderate Depression). Patient reports..."Anonymizer
Detects and reversibly replaces PII: names, dates of birth, addresses, phone numbers,
IBANs, insurance IDs, email addresses. The Chat endpoint does this automatically —
call /anonymize directly only for custom pipelines.
# Anonymize r = requests.post(f"{BASE}/anonymize", headers=headers, json={"text": "Patient Maria Mueller, DOB 15.03.1985, Berlin."}) anon = r.json()["data"] # anon["anonymized_text"] -> "Patient [PERSON_1], DOB [DATUM_1], [ORT_1]." # anon["mappings_encrypted"] -> "eyJ..." -- store this to restore names later # Deanonymize r2 = requests.post(f"{BASE}/deanonymize", headers=headers, json={ "anonymized_text": anon["anonymized_text"], "mappings_encrypted": anon["mappings_encrypted"], }) # -> "Patient Maria Mueller, DOB 15.03.1985, Berlin."
Evidence Database
Browse ICD-10-GM disorders, clinical guidelines, recommended interventions, and assessment tools. GET endpoints are free; full-text search costs 10 ct.
# Paginated disorder list (free) requests.get(f"{BASE}/evidence/disorders?page=1&per_page=20", headers=headers) # Disorder detail by ICD-10 code (free) requests.get(f"{BASE}/evidence/disorders/F32", headers=headers) # Recommended interventions (free) requests.get(f"{BASE}/evidence/interventions/F32", headers=headers) # Full-text search (10 ct) requests.get(f"{BASE}/evidence/search?q=depression", headers=headers)
Document Generator
Generate clinical documents from 11 validated templates — session notes, risk assessments, treatment plans, and more. PDF export available.
# List templates requests.get(f"{BASE}/generate/templates", headers=headers) # Generate document (20 ct) r = requests.post(f"{BASE}/generate/document", headers=headers, json={ "template_id": "verlaufsdokumentation_enhanced", "context": { "patient_info": "35M, F32.1", "session_number": 5, "content": "Patient reports improved sleep. Exposure exercises successful.", }, }) # r.json()["data"]["document"] -> ready-to-use clinical text
Text Improve
Convert rough notes and shorthand into professional clinical documentation.
Uses dkts_base_mini — the smallest, fastest model (EU Frankfurt, GDPR Art. 44-49).
Output is always in German clinical language regardless of input language.
Output styles
| Style | Format |
|---|---|
session_notes | Session note — neutral, descriptive, present tense |
report | Assessment report — formal, structured, past tense |
letter | Referral letter — very formal, full sentences |
diagnosis | Diagnostic formulation with explicit ICD-10-GM codes |
general (default) | General clinical language |
r = requests.post(f"{BASE}/text/improve", headers=headers, json={ "text": "Pat. 34F, F32.1, 3 Monate, Schlafprob., Antriebslos.", "style": "session_notes", }) d = r.json()["data"] # d["improved"] -> professional clinical text in German # d["original"] -> your input returned unchanged print(d["improved"])
context field to hint at where the text will appear
(e.g. "Treatment plan section 3: Goal definition") for better-calibrated output.Extended Thinking
Add thinking: true to any Chat request to enable step-by-step reasoning.
The AI works through the problem explicitly before responding — useful for complex
diagnostic questions or treatment planning.
| Parameter | Type | Default | Notes |
|---|---|---|---|
thinking | boolean | false | Adds +15 ct surcharge |
thinking_budget_tokens | integer | 10000 | 1024 – 32000. Higher = deeper reasoning |
response = requests.post(
"https://api.duktus-pro.de/api/v1/chat",
headers=headers,
json={
"message": "Patient shows F32.1 with somatic symptoms. PHQ-9: 18. GAD-7: 11. "
"Suggest a structured 12-session CBT plan including crisis protocol.",
"mode": "therapieplanung",
"thinking": True,
"thinking_budget_tokens": 16000,
}
)
data = response.json()
# Full reasoning trace (step-by-step)
print(data["thinking"])
# Final answer
print(data["message"])
curl -X POST https://api.duktus-pro.de/api/v1/chat \ -H "X-API-Key: $DUKTUS_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "message": "Patient F32.1, PHQ-9 18, GAD-7 11. Suggest 12-session CBT plan.", "mode": "therapieplanung", "thinking": true, "thinking_budget_tokens": 16000 }'
therapieplanung = 20 ct + 15 ct thinking surcharge = 35 ct total.
Use standard mode (15 ct + 15 ct = 30 ct) for general thinking tasks.Error Handling
All errors follow a consistent format with a machine-readable error.type and a
human-readable error.message. Every response includes a request_id
— include it when contacting support.
{
"type": "error",
"error": {
"type": "invalid_request_error",
"message": "Field 'text' is required."
},
"request_id": "req_01abc..."
}
| Status | Meaning | Action |
|---|---|---|
400 | Bad Request | Fix your request body |
401 | Unauthorized | Check your X-API-Key |
402 | Budget Exceeded | Contact DUKTUS to increase your budget |
404 | Not Found | Check endpoint path and resource ID |
422 | Validation Error | Check field types and allowed values |
429 | Rate Limited | Wait Retry-After seconds, then retry |
500 | Server Error | Retry once; contact api@duktus-pro.de with request_id |
import time, requests def call_api(method, url, **kwargs): r = requests.request(method, url, **kwargs) if r.status_code == 429: time.sleep(int(r.headers.get("Retry-After", 60))) return call_api(method, url, **kwargs) # retry once if r.status_code == 402: raise Exception("Monthly budget exceeded") r.raise_for_status() return r.json()
Rate Limits
Default: 100 requests/minute, 10,000/day per partner. Every response includes rate limit headers:
X-RateLimit-Limit-Minute: 100
X-RateLimit-Remaining-Minute: 87
X-RateLimit-Limit-Day: 10000
X-RateLimit-Remaining-Day: 9842
Retry-After: 3 # only on 429
Pricing
Pay-per-use in EUR cents. Every response includes usage.cost_cents.
HTTP 402 is returned when your monthly budget is reached.
| Endpoint | Price per call |
|---|---|
POST /chat (standard) | 15 ct |
POST /chat (diagnostik, therapieplanung, krisenintervention) | 20 ct |
POST /chat (dokumentation) | 8 ct |
POST /assessments/score, /tools | Free |
POST /anonymize | 3 ct |
POST /voice/transcribe (fast — outside EU, not for patient data) | 12 ct |
POST /voice/transcribe (private — EU Frankfurt, GDPR-compliant) | 18 ct |
GET /evidence/disorders/*, /interventions/*, /guidelines | Free |
GET /evidence/search | 10 ct |
POST /generate/document | 20 ct |
POST /text/improve | 6 ct dkts_base_mini |