DUBA API Integration Guide
A practical guide for integration partners using the DUBA (Digital Court Guardianship) API.
Version: 0.2.1 Last Updated: 2026-02-19
Table of Contents
- Introduction
- Authentication
- Core Concepts
- List Messages Endpoint
- Understanding MessageInfo
- Download Message Endpoint
- Acknowledge Messages Endpoint
- Form Pre-fill with Memento Endpoint
- Complete Workflow Examples
- Filtering Best Practices
- Error Codes
- Reference
Introduction
The DUBA API provides programmatic access to court messages exchanged via EGVP (Elektronisches Gerichts- und Verwaltungspostfach). This API allows integration partners to:
- List available messages with filtering by job ID, Safe-ID, or timestamp
- Download messages as ZIP files containing XJustiz XML and attachments
- Acknowledge messages to trigger cleanup of sensitive data (GDPR compliance)
- Create form mementos to generate pre-filled form URLs from your system data
Prerequisites
Before using the API, you need:
- API User Credentials: Username and password provided by your administrator
- Base URL: Your DUBA instance URL (e.g., https://elim.example.com)
- Safe-ID: EGVP Safe-ID(s) you have access to
API Versioning
All endpoints are versioned under /api/duba/v1/.
OpenAPI Specification
The complete OpenAPI specification is available at:
https://your-instance/api/docs/swagger-ui/index.html?urls.primaryName=DUBA
Authentication
The API uses HTTP Basic Authentication with your API user credentials.
Note on end-user authentication: The
magicLinkfield returned by the memento endpoint handles end-user authentication automatically via Magic Token Link (MTL). The Basic Auth Login (BAL) pattern (credentials embedded in URLs) is deprecated since v0.2.1 and should not be used for new integrations.
How It Works
- Your administrator creates an API user account with username and password
- For each API request, provide credentials in the
Authorizationheader - Internally, the system derives a KEK (Key Encryption Key) from your password for cryptographic operations, but you only need to provide your username and password
Example
curl -u "username:password" \
https://elim.example.com/api/duba/v1/messages
Or explicitly with Authorization header:
# Encode credentials (username:password in base64)
echo -n "username:password" | base64
# Result: dXNlcm5hbWU6cGFzc3dvcmQ=
curl -H "Authorization: Basic dXNlcm5hbWU6cGFzc3dvcmQ=" \
https://elim.example.com/api/duba/v1/messages
Core Concepts
Message States
Messages go through different states in the system:
- Indexed: Message metadata discovered from EGVP (lightweight, fast)
- Hydrated: Full message downloaded and XJustiz parsed (aktenzeichen extracted)
- Available: Message can be downloaded via API
Important: Only hydrated messages have complete metadata (aktenzeichen, sender info).
Job IDs vs Aktenzeichen
Two different identifiers are used:
- jobId: Your internal work tracking ID (e.g.,
"job-2024-001") - Arbitrary string meaningful to your system
- Used to correlate messages with your cases
-
Set when sending messages via DUBA
-
aktenzeichen: German legal case reference number (e.g.,
"AZ-12345-2024") - Formal XJustiz identifier required in court communications
- Extracted from message content after hydration
- Used by courts and legal systems
Mapping: The system maintains a mapping between your jobIds and aktenzeichen values.
Message Direction
Messages have a direction:
- INCOMING: Received from external courts or systems
- OUTGOING: Sent by you to external recipients
ProcessCard Timestamps
Messages include timestamps from the EGVP ProcessCard:
- createdAt (
tspCreation): When message arrived at EGVP server (always present) - receivedAt (
tspReception): When first retrieved by anyone - For INCOMING: When you or another system downloaded it
- For OUTGOING: When the recipient downloaded it
nullif not yet retrieved- hydratedAt: When the system fully processed the message (downloaded + parsed)
nullif not yet hydrated
List Messages Endpoint
Endpoint: GET /api/duba/v1/messages
Purpose: Query available messages with optional filtering.
Query Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
jobId |
array[string] | No | Filter by your job IDs. Can specify multiple times. |
safeId |
array[string] | No | Filter by EGVP Safe-IDs. Can specify multiple times. |
since |
string (ISO 8601) | No | Only messages created after this timestamp. |
Response
Returns an array of MessageInfo objects (see Understanding MessageInfo).
Examples
Get all available messages
curl -u "user:pass" \
https://elim.example.com/api/duba/v1/messages
Filter by single job ID
curl -u "user:pass" \
"https://elim.example.com/api/duba/v1/messages?jobId=job-2024-001"
Filter by multiple job IDs
curl -u "user:pass" \
"https://elim.example.com/api/duba/v1/messages?jobId=job-123&jobId=job-456"
Get recent messages (since timestamp)
curl -u "user:pass" \
"https://elim.example.com/api/duba/v1/messages?since=2025-11-25T10:00:00Z"
Combine multiple filters
curl -u "user:pass" \
"https://elim.example.com/api/duba/v1/messages?jobId=job-123&safeId=safe-abc&since=2025-11-25T10:00:00Z"
Filtering Behavior
Important semantic note:
- Without
jobIdfilter: Returns ALL messages (hydrated + non-hydrated) - With
jobIdfilter: Returns only messages for those jobs - Naturally returns only hydrated messages (because jobId mapping requires aktenzeichen)
- Non-hydrated messages cannot be matched to jobs yet (no aktenzeichen extracted)
This is intentional and correct behavior, not a bug.
Understanding MessageInfo
The API returns MessageInfo objects with complete message metadata.
Field Reference
| Field | Type | Nullable | Description |
|---|---|---|---|
id |
integer | No | Message index ID - use this for download |
messageId |
string | No | EGVP/Vibilia transport message ID |
jobId |
string | Yes | Your job ID (null for external messages) |
aktenzeichen |
string | Yes | Legal case number (null if not hydrated) |
direction |
enum | No | INCOMING or OUTGOING |
createdAt |
datetime | No | When message arrived at EGVP server |
receivedAt |
datetime | Yes | When first retrieved (null if not yet retrieved) |
hydratedAt |
datetime | Yes | When processed by system (null if not hydrated) |
url |
string (uri) | No | Relative URL for download endpoint |
Example Response
[
{
"id": 214,
"messageId": "gov2test_17623381049230a53d2f5-5015-4d9b-9afb-e86b1d0b6872",
"jobId": "job-2024-001",
"aktenzeichen": "AZ-12345-2024",
"direction": "INCOMING",
"createdAt": "2025-11-26T14:30:00Z",
"receivedAt": "2025-11-26T15:00:00Z",
"hydratedAt": "2025-11-26T15:01:00Z",
"url": "/api/duba/v1/download/214"
},
{
"id": 215,
"messageId": "msg-def-456",
"jobId": null,
"aktenzeichen": "AZ-67890-2024",
"direction": "INCOMING",
"createdAt": "2025-11-26T14:35:00Z",
"receivedAt": "2025-11-26T14:40:00Z",
"hydratedAt": "2025-11-26T14:41:00Z",
"url": "/api/duba/v1/download/215"
}
]
Understanding Nullable Fields
jobId == null
The message was not sent via your DUBA API integration. Possible reasons: - External court sent you an unsolicited message - Another system sharing your EGVP account sent the message - Message predates your API integration
aktenzeichen == null
The message is not yet hydrated (still processing). This means:
- Metadata is available (indexed)
- Full content not yet downloaded/parsed
- Cannot be matched to jobs yet
- Wait for hydratedAt != null before relying on aktenzeichen
receivedAt == null
The message has not been retrieved yet: - For INCOMING messages: Neither you nor any other system has downloaded it yet - For OUTGOING messages: The recipient hasn't downloaded it yet
Download Message Endpoint
Endpoint: GET /api/duba/v1/download/{id}
Purpose: Download a specific message as a ZIP file.
Path Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
id |
integer | Yes | Message index ID from MessageInfo.id |
Response
Returns a ZIP file containing: - XJustiz XML message file - Any attachments included with the message - Metadata files
Content-Type: application/zip
Examples
Download single message
curl -u "user:pass" \
https://elim.example.com/api/duba/v1/download/214 \
-o message-214.zip
Download with verbose output
curl -v -u "user:pass" \
https://elim.example.com/api/duba/v1/download/214 \
-o message-214.zip
Check download headers without downloading
curl -I -u "user:pass" \
https://elim.example.com/api/duba/v1/download/214
Acknowledge Messages Endpoint
Endpoint: POST /api/duba/v1/messages/ack
Purpose: Acknowledge receipt of one or more messages and trigger cleanup.
What This Does
When you acknowledge a message: 1. Deletes on-disk files - Sensitive message content is removed (data protection) 2. Soft-deletes index entry - Metadata is preserved for audit trail 3. Returns detailed status - Know exactly what happened to each message
Use case: After successfully downloading and processing messages, acknowledge them to trigger cleanup and comply with data protection requirements.
Request Body
{
"messageIds": [42, 43, 44]
}
| Field | Type | Required | Description |
|---|---|---|---|
messageIds |
integer[] | Yes | Array of message index IDs to acknowledge (1-100 items) |
Response
Returns status for each message ID:
{
"results": [
{
"id": 42,
"status": "DELETED",
"message": "Message acknowledged and deleted"
},
{
"id": 43,
"status": "ALREADY_DELETED",
"message": "Message was already deleted"
},
{
"id": 44,
"status": "FORBIDDEN",
"message": "User does not have access to this message"
}
]
}
Status Values
| Status | Meaning | Is Error? |
|---|---|---|
DELETED |
Successfully acknowledged and cleaned up | No |
ALREADY_DELETED |
Message was already deleted (idempotent) | No |
NOT_FOUND |
Message index entry does not exist | Yes |
FORBIDDEN |
User does not have access to this message | Yes |
ERROR |
An error occurred during processing | Yes |
Important: Partial success is allowed. Some messages may succeed while others fail. Always check the status for each ID.
Examples
Acknowledge single message
curl -X POST \
-u "user:pass" \
-H "Content-Type: application/json" \
-d '{"messageIds": [214]}' \
https://elim.example.com/api/duba/v1/messages/ack
Acknowledge multiple messages
curl -X POST \
-u "user:pass" \
-H "Content-Type: application/json" \
-d '{"messageIds": [214, 215, 216]}' \
https://elim.example.com/api/duba/v1/messages/ack
Parse response with jq
curl -X POST \
-u "user:pass" \
-H "Content-Type: application/json" \
-d '{"messageIds": [214, 215, 216]}' \
https://elim.example.com/api/duba/v1/messages/ack | jq .
Count successful deletions
curl -X POST \
-u "user:pass" \
-H "Content-Type: application/json" \
-d '{"messageIds": [214, 215, 216]}' \
https://elim.example.com/api/duba/v1/messages/ack | \
jq '[.results[] | select(.status == "DELETED")] | length'
Download and acknowledge workflow
#!/bin/bash
# List messages, download each, then acknowledge all at once
MESSAGE_IDS=$(curl -u "user:pass" \
"https://elim.example.com/api/duba/v1/messages?jobId=job-123" | \
jq -r '.[].id')
# Download each message
for id in $MESSAGE_IDS; do
echo "Downloading message $id..."
curl -u "user:pass" \
"https://elim.example.com/api/duba/v1/download/$id" \
-o "message-$id.zip"
done
# Acknowledge all messages at once (bulk operation)
curl -X POST \
-u "user:pass" \
-H "Content-Type: application/json" \
-d "{\"messageIds\": [$(echo $MESSAGE_IDS | tr '\n' ',' | sed 's/,$//' )]}" \
https://elim.example.com/api/duba/v1/messages/ack
Data Protection Notes
Acknowledging messages is one of three cleanup triggers in the system:
- ACK endpoint (this) - Client-driven, explicit confirmation
- Sync-based cleanup - Automatic when message confirmed gone from server
- Retention policy - Time-based backstop (e.g., 30 days)
Best practice: Acknowledge messages as soon as you've successfully downloaded and processed them. This ensures: - Sensitive data is deleted promptly (GDPR compliance) - Your message list stays clean - Audit trail is preserved for compliance
Idempotent operation: Safe to acknowledge the same message multiple times. Already-deleted messages return ALREADY_DELETED status (not an error).
Form Pre-fill with Memento Endpoint
Endpoint: POST /api/duba/v1/memento
Purpose: Create encrypted memento strings to pre-fill DUBA web forms with data from your system.
What This Does
The memento endpoint solves a common integration pattern:
- Your system (e.g., KIS) has complete case data but wants users to review/approve before submission
- You call this endpoint with the form data as JSON
- You receive back an encrypted memento string and a ready-to-use
magicLinkURL - You send the
magicLinkto the end user (email, portal link, etc.) - Users click the link → authenticate automatically → see the pre-filled form
Benefits:
- No direct submission required — users maintain control
- Form validation happens in the browser (immediate feedback)
- Users can correct or supplement data before sending
- Encrypted mementos are tamper-proof
- magicLink handles authentication automatically — no credentials in the URL
Use Case Example
Hospital KIS → Knows patient details for court guardianship request
→ Calls /memento with patient/case data as JSON
→ Receives { "memento": "...", "magicLink": "/mtl/.../duba/form?m=..." }
→ Prepends host: https://elim.example.com + magicLink
→ Emails absolute URL to authorized user
User → Clicks link (no login required)
→ Automatically authenticated
→ Sees pre-filled form with all case details
→ Reviews, corrects if needed, submits to court
Request Body
The endpoint accepts DUBA form data as JSON. All fields except jobId are optional.
Required Fields:
- jobId - Your internal tracking ID (string)
- absender.egvp_account_id - EGVP/beBPo account ID for submission (integer)
Common Structure:
{
"jobId": "job-2024-12345",
"meldeZeitpunkt": "2025-12-10T14:30:00+01:00",
"absender": {
"name": "Klinikum Musterstadt",
"aktenzeichen": "KH-2024-001",
"egvp_account_id": 42
},
"empfaenger": {
"name": "Amtsgericht Musterstadt",
"type": "Gericht",
"safeId": "gov2test",
"aktenzeichen": "AZ-2024-67890",
"adresse": {
"strasse": "Gerichtsplatz 1",
"plz": "12345",
"stadt": "Musterstadt"
}
},
"betroffener": {
"name": {
"vorname": "Max",
"nachname": "Mustermann"
},
"geburtsdatum": "1950-01-15",
"familienstand": "Verheiratet",
"anschrift": {
"strasse": "Musterstraße 42",
"plz": "12345",
"stadt": "Musterstadt"
},
"anschriftTelefon": "+49 123 456789",
"gegenwaertigerAufenthalt": "Klinik XY, Station 3, Zimmer 12"
}
}
Field Reference
| Field | Type | Required | Description |
|---|---|---|---|
jobId |
string | Yes | Your internal job/case tracking ID |
meldeZeitpunkt |
datetime (ISO 8601) | No | Timestamp of report/registration |
absender.name |
string | No | Sender organization name |
absender.aktenzeichen |
string | No | Your internal case reference |
absender.egvp_account_id |
integer | Yes* | EGVP beBPo account ID for submission |
empfaenger.name |
string | No | Recipient organization name |
empfaenger.type |
enum | No | Gericht or Sonstige |
empfaenger.safeId |
string | No | EGVP Safe-ID for recipient |
empfaenger.aktenzeichen |
string | No | Court case reference number |
empfaenger.adresse.* |
object | No | Recipient address (strasse, plz, stadt) |
betroffener.name.* |
object | No | Affected person's name (vorname, nachname) |
betroffener.geburtsdatum |
date (YYYY-MM-DD) | No | Date of birth |
betroffener.familienstand |
enum | No | Ledig, Verheiratet, Geschieden, Verwitwet |
betroffener.anschrift.* |
object | No | Home address |
betroffener.anschriftTelefon |
string | No | Home phone number |
betroffener.gegenwaertigerAufenthalt |
string | No | Current location (e.g., hospital, ward, room) |
* Required for actual form submission, but optional for memento creation
Response
Returns a JSON object containing the encrypted memento string and a ready-to-use magic link:
{
"memento": "eyJhbGciOiJkaXIiLCJlbmMiOiJBMjU2R0NNIn0..DGG5lQvJC8OpYrCt.Xm8YR...",
"magicLink": "/mtl/eyJ...token.../duba/?m=eyJ...memento..."
}
| Field | Type | Nullable | Description |
|---|---|---|---|
memento |
string | No | Encrypted, URL-safe string containing your form data |
magicLink |
string | Yes | Relative URL that authenticates and opens the pre-filled form in one click. Prepend your host: https://your-instance + magicLink |
Note: Memento strings are typically 500-2000 characters depending on data size. They are encrypted with AES-256-GCM and safe to pass via URL parameters. The magicLink token expires after 1 hour by default.
Examples
Minimal example (jobId only)
curl -X POST \
-u "user:pass" \
-H "Content-Type: application/json" \
-d '{"jobId":"job-2024-001","absender":{"egvp_account_id":42}}' \
https://elim.example.com/api/duba/v1/memento
Response:
{
"memento": "eyJhbGciOiJkaXIiLCJlbmMiOiJBMjU2R0NNIn0..abc123...",
"magicLink": "/mtl/eyJ...token.../duba/?m=eyJ...memento..."
}
Complete guardianship request example
curl -X POST \
-u "user:pass" \
-H "Content-Type: application/json" \
-d '{
"jobId": "job-2024-12345",
"meldeZeitpunkt": "2025-12-10T14:30:00+01:00",
"absender": {
"name": "Klinikum Musterstadt",
"aktenzeichen": "KH-2024-001",
"egvp_account_id": 42
},
"empfaenger": {
"name": "Amtsgericht Musterstadt",
"type": "Gericht",
"safeId": "gov2test",
"aktenzeichen": "AZ-2024-67890"
},
"betroffener": {
"name": {
"vorname": "Max",
"nachname": "Mustermann"
},
"geburtsdatum": "1950-01-15",
"familienstand": "Verheiratet",
"anschrift": {
"strasse": "Musterstraße 42",
"plz": "12345",
"stadt": "Musterstadt"
},
"anschriftTelefon": "+49 123 456789",
"gegenwaertigerAufenthalt": "Klinik XY, Station 3, Zimmer 12"
}
}' \
https://elim.example.com/api/duba/v1/memento
Extract memento and magic link with jq
RESPONSE=$(curl -s -X POST \
-u "user:pass" \
-H "Content-Type: application/json" \
-d '{"jobId":"job-123","absender":{"egvp_account_id":42}}' \
https://elim.example.com/api/duba/v1/memento)
MEMENTO=$(echo "$RESPONSE" | jq -r '.memento')
MAGIC_LINK=$(echo "$RESPONSE" | jq -r '.magicLink')
echo "Memento: $MEMENTO"
echo "Magic link (relative): $MAGIC_LINK"
echo "Full URL: https://elim.example.com$MAGIC_LINK"
Using the Magic Link (Recommended)
The API response includes a magicLink field — a relative URL that handles both authentication
and form pre-fill in a single click. This is the recommended approach for giving end users access
to pre-filled forms.
The magicLink is a relative path. Prepend your instance host to make it absolute:
RESPONSE=$(curl -s -X POST -u "user:pass" \
-H "Content-Type: application/json" \
-d "$FORM_DATA" \
"https://elim.example.com/api/duba/v1/memento")
MAGIC_LINK=$(echo "$RESPONSE" | jq -r '.magicLink')
FULL_URL="https://elim.example.com$MAGIC_LINK"
# Send FULL_URL to the end user
echo "$FULL_URL"
When the end user opens this URL: 1. The server decrypts the embedded token 2. The user is authenticated automatically (no login page) 3. They land directly on the pre-filled form
Token expiry: Magic links expire after 1 hour by default. Generate a fresh link close to the time you send it to the user.
Alternative: Manual Form URL
If the end user already has an active ELIM session (e.g., they are already logged in), you can
construct a bare form URL using the memento field:
URL Pattern:
https://your-instance/duba/{FormName}?m={memento}
Available Form Types:
| Form Name | Purpose |
|---|---|
BetreuungAnregung |
Guardianship suggestion/request |
UnterbringungAntrag |
Involuntary commitment application |
FreiheitsentzugAntrag |
Deprivation of liberty application |
Example:
MEMENTO=$(echo "$RESPONSE" | jq -r '.memento')
FORM_URL="https://elim.example.com/duba/BetreuungAnregung?m=$MEMENTO"
# Use this only if the user already has an active session
Note: Bare form URLs require the user to be logged in. If the user is not authenticated, the
server will redirect to /login. Use the magicLink approach if you cannot guarantee the user
has an active session.
Complete End-to-End Example
#!/bin/bash
# Complete workflow: Create memento and send magic link to end user
BASE_URL="https://elim.example.com"
USER="your-username"
PASS="your-password"
# Step 1: Prepare form data
FORM_DATA='{
"jobId": "job-2026-12345",
"meldeZeitpunkt": "2026-02-19T14:30:00+01:00",
"absender": {
"aktenzeichen": "KH-2024-001",
"egvp_account_id": 42
},
"empfaenger": {
"name": "Amtsgericht Musterstadt",
"type": "Gericht",
"safeId": "de.justiz.bund.egvp.bea.12345"
},
"betroffener": {
"name": {"vorname": "Max", "nachname": "Mustermann"},
"geburtsdatum": "1950-01-15",
"anschrift": {
"strasse": "Musterstraße 42",
"plz": "12345",
"stadt": "Musterstadt"
}
}
}'
# Step 2: Call memento API
echo "Creating memento..."
RESPONSE=$(curl -s -X POST \
-u "$USER:$PASS" \
-H "Content-Type: application/json" \
-d "$FORM_DATA" \
"$BASE_URL/api/duba/v1/memento")
# Step 3: Extract magic link from response
MAGIC_LINK=$(echo "$RESPONSE" | jq -r '.magicLink')
if [ -z "$MAGIC_LINK" ] || [ "$MAGIC_LINK" = "null" ]; then
echo "Error: Failed to create memento"
echo "$RESPONSE" | jq .
exit 1
fi
# Step 4: Construct absolute URL (magicLink is relative)
FULL_URL="$BASE_URL$MAGIC_LINK"
echo "Success! Send this URL to the authorized user:"
echo "$FULL_URL"
echo ""
echo "The link authenticates automatically and opens the pre-filled form."
echo "Note: This link expires in 1 hour."
Integration Patterns
Pattern 1: Email with magic link (Recommended)
# Create memento and email the magic link to user
RESPONSE=$(curl -s -X POST -u "$USER:$PASS" \
-H "Content-Type: application/json" \
-d "$FORM_DATA" \
"$BASE_URL/api/duba/v1/memento")
MAGIC_LINK=$(echo "$RESPONSE" | jq -r '.magicLink')
FULL_URL="$BASE_URL$MAGIC_LINK"
# Email the magic link (example using mail command)
mail -s "Court Form Ready for Review" user@hospital.example.com <<EOF
Please review and submit the guardianship form:
$FULL_URL
Click the link to open the pre-filled form directly. No login required.
Note: This link expires in 1 hour.
EOF
Pattern 2: Generate QR code for mobile access
# Create memento and generate QR code from magic link
MAGIC_LINK=$(curl -s -X POST -u "$USER:$PASS" \
-H "Content-Type: application/json" \
-d "$FORM_DATA" \
"$BASE_URL/api/duba/v1/memento" | jq -r '.magicLink')
FULL_URL="$BASE_URL$MAGIC_LINK"
# Generate QR code (requires qrencode tool)
echo "$FULL_URL" | qrencode -o form-qr.png
echo "QR code saved to form-qr.png"
Pattern 3: Batch form generation
# Generate magic links for multiple cases from CSV
while IFS=, read -r case_id patient_name dob; do
FORM_DATA=$(jq -n \
--arg jobId "$case_id" \
--arg vorname "$(echo $patient_name | cut -d' ' -f1)" \
--arg nachname "$(echo $patient_name | cut -d' ' -f2)" \
--arg dob "$dob" \
'{
jobId: $jobId,
absender: {egvp_account_id: 42},
betroffener: {
name: {vorname: $vorname, nachname: $nachname},
geburtsdatum: $dob
}
}')
MAGIC_LINK=$(curl -s -X POST -u "$USER:$PASS" \
-H "Content-Type: application/json" \
-d "$FORM_DATA" \
"$BASE_URL/api/duba/v1/memento" | jq -r '.magicLink')
echo "$case_id,$BASE_URL$MAGIC_LINK"
done < cases.csv > form_links.csv
echo "Generated form links saved to form_links.csv"
# Note: links expire after 1 hour — generate close to the time of sending
Error Handling
Validation Errors
If the request data is invalid, you'll receive a 400 Bad Request with details:
curl -X POST -u "user:pass" \
-H "Content-Type: application/json" \
-d '{"invalid":"data"}' \
https://elim.example.com/api/duba/v1/memento
Response:
{
"error": "Validation failed",
"errors": [
"Field 'jobId' is required"
]
}
Handle Errors in Scripts
RESPONSE=$(curl -s -X POST -u "$USER:$PASS" \
-H "Content-Type: application/json" \
-d "$FORM_DATA" \
"$BASE_URL/api/duba/v1/memento")
# Check for error field
if echo "$RESPONSE" | jq -e '.error' > /dev/null; then
echo "Error creating memento:"
echo "$RESPONSE" | jq -r '.error'
if echo "$RESPONSE" | jq -e '.errors' > /dev/null; then
echo "Details:"
echo "$RESPONSE" | jq -r '.errors[]'
fi
exit 1
fi
# Extract memento
MEMENTO=$(echo "$RESPONSE" | jq -r '.memento')
echo "Success: $MEMENTO"
Security Notes
Memento Encryption: - Mementos are encrypted with AES-256-GCM using your API user's KEK (Key Encryption Key) - Each memento is unique even for identical data (includes random IV) - Mementos cannot be decrypted without the correct API user credentials - Tampering is detected and rejected
Best Practices: - ✓ Mementos are safe to pass via URL parameters - ✓ Mementos can be safely logged or stored - ✓ Mementos expire after a reasonable time (check instance configuration) - ✗ Don't expose mementos to unauthorized users (they contain sensitive patient data) - ✗ Don't reuse mementos across different forms or cases
Data Minimization: - Only include data that's actually needed for the form - Empty/null fields don't bloat the memento (they're omitted) - Minimal mementos result in shorter, more manageable URLs
Complete Workflow Examples
Workflow 1: Poll for new messages and download them
#!/bin/bash
# Configuration
BASE_URL="https://elim.example.com"
USER="your-username"
PASS="your-password"
SINCE="2025-11-25T10:00:00Z"
# Fetch messages since last check
echo "Fetching messages since $SINCE..."
MESSAGES=$(curl -s -u "$USER:$PASS" \
"$BASE_URL/api/duba/v1/messages?since=$SINCE")
# Check if we got any messages
COUNT=$(echo "$MESSAGES" | jq '. | length')
echo "Found $COUNT messages"
# Download each message
echo "$MESSAGES" | jq -r '.[].id' | while read ID; do
echo "Downloading message $ID..."
curl -s -u "$USER:$PASS" \
"$BASE_URL/api/duba/v1/download/$ID" \
-o "message-$ID.zip"
if [ $? -eq 0 ]; then
echo " ✓ Downloaded message-$ID.zip"
else
echo " ✗ Failed to download message $ID"
fi
done
echo "Done!"
Workflow 2: Download all messages for a specific job
#!/bin/bash
# Configuration
BASE_URL="https://elim.example.com"
USER="your-username"
PASS="your-password"
JOB_ID="job-2024-001"
# Create output directory
mkdir -p "downloads/$JOB_ID"
# List messages for job
echo "Fetching messages for job $JOB_ID..."
MESSAGES=$(curl -s -u "$USER:$PASS" \
"$BASE_URL/api/duba/v1/messages?jobId=$JOB_ID")
# Download each
echo "$MESSAGES" | jq -r '.[].id' | while read ID; do
echo "Downloading message $ID..."
curl -s -u "$USER:$PASS" \
"$BASE_URL/api/duba/v1/download/$ID" \
-o "downloads/$JOB_ID/message-$ID.zip"
done
echo "All messages for $JOB_ID downloaded to downloads/$JOB_ID/"
Workflow 3: Check for INCOMING court responses
#!/bin/bash
# Configuration
BASE_URL="https://elim.example.com"
USER="your-username"
PASS="your-password"
# Fetch all messages
echo "Checking for incoming court responses..."
MESSAGES=$(curl -s -u "$USER:$PASS" \
"$BASE_URL/api/duba/v1/messages")
# Filter INCOMING messages and download
echo "$MESSAGES" | jq -r '.[] | select(.direction == "INCOMING") | .id' | while read ID; do
# Get message details
MSG=$(echo "$MESSAGES" | jq -r ".[] | select(.id == $ID)")
AKTENZEICHEN=$(echo "$MSG" | jq -r '.aktenzeichen // "unknown"')
echo "Downloading incoming message $ID (AZ: $AKTENZEICHEN)..."
curl -s -u "$USER:$PASS" \
"$BASE_URL/api/duba/v1/download/$ID" \
-o "incoming-$AKTENZEICHEN-$ID.zip"
done
echo "Done!"
Workflow 4: Incremental polling with state tracking
#!/bin/bash
# Configuration
BASE_URL="https://elim.example.com"
USER="your-username"
PASS="your-password"
STATE_FILE=".last_poll_timestamp"
# Read last poll timestamp (or use default)
if [ -f "$STATE_FILE" ]; then
SINCE=$(cat "$STATE_FILE")
echo "Polling since last check: $SINCE"
else
SINCE="2025-11-01T00:00:00Z"
echo "First poll, using: $SINCE"
fi
# Fetch new messages
MESSAGES=$(curl -s -u "$USER:$PASS" \
"$BASE_URL/api/duba/v1/messages?since=$SINCE")
# Process messages
echo "$MESSAGES" | jq -r '.[].id' | while read ID; do
echo "Processing message $ID..."
curl -s -u "$USER:$PASS" \
"$BASE_URL/api/duba/v1/download/$ID" \
-o "message-$ID.zip"
done
# Update timestamp for next poll
date -u +"%Y-%m-%dT%H:%M:%SZ" > "$STATE_FILE"
echo "Updated last poll timestamp"
Filtering Best Practices
Use jobId filtering for targeted queries
When you know which cases you're interested in, filter by jobId to reduce response size and processing:
curl -u "$USER:$PASS" \
"$BASE_URL/api/duba/v1/messages?jobId=job-123&jobId=job-456"
Use since parameter for incremental polling
Avoid re-fetching all messages by using the since parameter:
# First poll: get everything
curl -u "$USER:$PASS" "$BASE_URL/api/duba/v1/messages"
# Subsequent polls: only new messages
LAST_POLL="2025-11-26T10:00:00Z"
curl -u "$USER:$PASS" "$BASE_URL/api/duba/v1/messages?since=$LAST_POLL"
Combine filters for precise queries
Combine multiple filters to narrow results:
# Only messages for specific jobs in a specific Safe-ID since yesterday
curl -u "$USER:$PASS" \
"$BASE_URL/api/duba/v1/messages?jobId=job-123&safeId=safe-abc&since=2025-11-26T00:00:00Z"
Handle nullable fields gracefully
Always check for null values before using fields:
# Using jq to safely handle nulls
curl -s -u "$USER:$PASS" "$BASE_URL/api/duba/v1/messages" | \
jq -r '.[] | "\(.id): jobId=\(.jobId // "none"), aktenzeichen=\(.aktenzeichen // "not-hydrated")"'
Filter by direction in client code
Since the API doesn't have a direction parameter, filter client-side:
# Get only INCOMING messages
curl -s -u "$USER:$PASS" "$BASE_URL/api/duba/v1/messages" | \
jq '.[] | select(.direction == "INCOMING")'
Error Codes
The API uses standard HTTP status codes:
| Code | Status | Meaning |
|---|---|---|
| 200 | OK | Request successful |
| 400 | Bad Request | Invalid parameters (e.g., malformed timestamp) |
| 401 | Unauthorized | Missing or invalid credentials |
| 403 | Forbidden | No access to this message or account |
| 404 | Not Found | Message does not exist |
| 500 | Internal Server Error | Server error (contact support) |
Example Error Response
# Invalid timestamp format
curl -u "$USER:$PASS" \
"$BASE_URL/api/duba/v1/messages?since=invalid-date"
# Response: 400 Bad Request
Handling Errors in Scripts
# Check HTTP status code
HTTP_CODE=$(curl -s -o response.json -w "%{http_code}" -u "$USER:$PASS" \
"$BASE_URL/api/duba/v1/messages")
if [ "$HTTP_CODE" -eq 200 ]; then
echo "Success!"
cat response.json | jq .
elif [ "$HTTP_CODE" -eq 401 ]; then
echo "Authentication failed - check credentials"
elif [ "$HTTP_CODE" -eq 403 ]; then
echo "Access denied - check permissions"
else
echo "Error: HTTP $HTTP_CODE"
fi
Reference
API Endpoints Summary
| Method | Endpoint | Description |
|---|---|---|
| GET | /api/duba/v1/messages |
List available messages |
| GET | /api/duba/v1/download/{id} |
Download message as ZIP |
| POST | /api/duba/v1/messages/ack |
Acknowledge messages (trigger cleanup) |
| POST | /api/duba/v1/memento |
Create encrypted memento for form pre-fill |
OpenAPI Specification
Interactive API documentation (Swagger UI):
https://your-instance/api/docs/swagger-ui/index.html?urls.primaryName=DUBA
Timestamp Format
All timestamps use ISO 8601 format with UTC timezone:
YYYY-MM-DDTHH:MM:SSZ
Example: 2025-11-26T15:34:10Z
Related Documentation
- Magic Token Link (MTL) — How
/mtl/authentication tokens work - Basic Auth Login (BAL) — Deprecated since v0.2.1. Replaced by Magic Token Link.
Support
For technical support or questions about the DUBA API: - Contact your system administrator - Refer to the OpenAPI specification for detailed schema documentation
Document Version: 0.2.1 Last Updated: 2026-02-19 API Version: v1