Field Service Voice Intake

A service technician finishes a job (an HVAC unit installation, a machine repair, an on-site inspection) and dictates a short voice note describing what they did, what materials and tools they used, and any follow-up needed. Your ERP turns that into a structured work report ready for invoicing, with services, materials and tools mapped to your service catalogue.

The workflow at a glance

┌──────────────┐    ┌────────────┐    ┌──────────┐
│ Technician   │    │ Your ERP   │    │ xander   │
└──────┬───────┘    └─────┬──────┘    └─────┬────┘
       │ voice note       │                 │
       │─────────────────>│                 │
       │                  │ 1. transcribe   │
       │                  │────────────────>│
       │                  │   transcript    │
       │                  │<────────────────│
       │                  │                 │
       │                  │ 2. summarize +  │
       │                  │    classify     │
       │                  │────────────────>│
       │                  │   summary +     │
       │                  │   coarse cats   │
       │                  │<────────────────│
       │                  │                 │
       │                  │ 3. SELECT FROM  │
       │                  │    catalog WHERE│
       │                  │    cat IN (...) │
       │                  │ (your DB)       │
       │                  │                 │
       │                  │ 4a. identify    │
       │                  │     services    │
       │                  │────────────────>│
       │                  │ 4b. identify    │
       │                  │     materials   │
       │                  │────────────────>│
       │                  │ 4c. identify    │
       │                  │     tools       │
       │                  │────────────────>│
       │                  │   3 results     │
       │                  │<────────────────│
       │                  │                 │
       │                  │ persist work    │
       │                  │ report          │
Why this workflow looks the way it does

xander is intentionally stateless and does not connect to your catalogue or master data. Quality of category- and item-identification scales inversely with the size of the candidate set we have to consider, give us 50,000 catalogue items in step 4 and the model will hallucinate or pick badly; give us 80 items pre-filtered by the category we identified in step 2 and accuracy goes way up.

That is why the recommended pattern is: (1) transcribe, (2) let xander do a coarse classification first, (3) use that classification in your own system to narrow the candidate catalogue, (4) hand the reduced catalogue back to xander as context for the precise identification step. Skipping the reduction in step 3 will work, but precision drops noticeably as your catalogue grows.

Step 1: Transcribe the voice note

For a typical field-service voice note (under 20 MB, under 20 minutes) use the synchronous POST /prompt/audioTranscription endpoint. Send the audio bytes as base64 in the request body. xander returns the transcript directly.

curl -X POST "$XANDER_BASE_URL/prompt/audioTranscription" \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "AudioInput": {
      "AudioFile": {
        "Base64AudioFile": "<base64-encoded audio bytes>",
        "FileName": "job-2026-04-11-1430.m4a",
        "AudioType": "audio/mp4"
      }
    },
    "Options": {
      "PromptIdentification": {
        "ProjectId": "your-project-id",
        "Promptname": "field-service/audio_transcription"
      },
      "SessionMetadata": {
        "UseCaseName": "field-service-voice-intake",
        "UseCaseSessionId": "<your correlation id, e.g. job ticket id>"
      }
    },
    "TranscriptionOptions": {
      "AudioLanguage":  { "LanguageCodeIso639": "de" },
      "TargetLanguage": { "LanguageCodeIso639": "de" }
    }
  }'

Response shape:

{
  "transcription": "Wartung HVAC Anlage abgeschlossen, Filter F7 ersetzt, ...",
  "gptResult": { "plainResponse": "..." },
  "metaInformation": { "retries": 0, "appliedModel": "..." }
}

Set UseCaseSessionId to your own ticket / job ID. Every subsequent call in this workflow should reuse the same value, that's how we tie all the calls of one voice intake together in our internal logging, which makes support requests much faster to debug.

When the audio is large

If the recording is longer than ~20 minutes, switch to the async transcription endpoint POST /prompt-async/audioTranscription-large and poll for the result. See the Meeting Helper recipe for the polling pattern.

Step 2: Generate a summary and identify coarse categories

Send the transcript to POST /prompt/informationextraction with a free-form summarisation prompt. xander returns a human-readable work report and a coarse classification of which categories of catalogue entries are likely relevant (e.g. "HVAC maintenance, filter replacement; air handler"). You will use that classification in step 3 to filter your own catalogue.

curl -X POST "$XANDER_BASE_URL/prompt/informationextraction" \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "UserQuery": {
      "UserQueryText": "Wartung HVAC Anlage abgeschlossen, Filter F7 ersetzt, ..."
    },
    "QueryParameter": [],
    "Options": {
      "PromptIdentification": {
        "ProjectId": "your-project-id",
        "Promptname": "field-service/work_report_summary"
      },
      "SessionMetadata": {
        "UseCaseName": "field-service-voice-intake",
        "UseCaseSessionId": "<same correlation id as step 1>"
      }
    }
  }'

The exact shape of the response is determined by the prompt template your xander contact provisions for you. Typical output is a Markdown report plus a JSON list of category hints, but you can ask for whatever fits your domain.

Step 3: Reduce your catalogue (in your own system)

This step does not involve xander. In your ERP, run a query against your service catalogue, materials catalogue and tools catalogue, filtered by the categories from step 2. Pseudocode:

SELECT articleNumber, name, unit
FROM service_catalog
WHERE category IN ('hvac.maintenance', 'hvac.filter_replacement');

SELECT articleNumber, name, unit
FROM material_catalog
WHERE category IN ('hvac.consumables', 'hvac.filters');

SELECT articleNumber, name
FROM tool_catalog
WHERE category IN ('hvac.handheld', 'hvac.measurement');

Aim for a few dozen to a few hundred candidates per category, not the entire catalogue. The reduced lists are what you pass back to xander in step 4 as the Leistungskatalog, Materialkatalog and tools catalogue QueryParameter values.

Step 4: Identify services, materials and tools (in parallel)

Three independent POST /prompt/informationextraction-structured calls, each scoped to one category. They have no dependency on each other and you can fire them in parallel. Each call gets the transcript plus the relevant reduced catalogue, and a JSON schema describing the shape you want the result in.

Step 4a: Identify services

curl -X POST "$XANDER_BASE_URL/prompt/informationextraction-structured" \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "UserQuery": { "UserQueryText": "" },
    "QueryParameter": [
      { "Name": "Leistungskatalog",  "Value": "<reduced services catalogue as text or TOON>" },
      { "Name": "Leistungsbeschrieb", "Value": "<the transcript from step 1>" }
    ],
    "Options": {
      "PromptIdentification": {
        "ProjectId": "your-project-id",
        "Promptname": "field-service/identify_services"
      },
      "SessionMetadata": {
        "UseCaseName": "field-service-voice-intake",
        "UseCaseSessionId": "<same correlation id>"
      }
    },
    "OutputStructure": {
      "JsonSchemaDefinition": "{\"type\":\"object\",\"properties\":{\"services\":{\"type\":\"array\",\"items\":{\"$ref\":\"#/$defs/Service\"}}},\"required\":[\"services\"],\"additionalProperties\":false,\"$defs\":{\"Service\":{\"type\":\"object\",\"properties\":{\"articleNumber\":{\"type\":\"string\",\"description\":\"Unique identifier of the service from the reduced catalogue\"},\"name\":{\"type\":\"string\"},\"reason\":{\"type\":\"string\",\"description\":\"Why this service was identified\"},\"confidence\":{\"type\":\"number\",\"description\":\"1=low, 5=medium, 10=high\"},\"quantity\":{\"type\":\"number\"}},\"required\":[\"articleNumber\",\"name\",\"reason\",\"confidence\",\"quantity\"],\"additionalProperties\":false}}}"
    }
  }'

Response shape:

{
  "result": {
    "services": [
      {
        "articleNumber": "SVC-HVAC-MAINT-STD",
        "name": "Standard HVAC maintenance visit",
        "reason": "The technician explicitly mentioned a complete maintenance run.",
        "confidence": 9,
        "quantity": 1
      },
      {
        "articleNumber": "SVC-HVAC-FILTER-F7",
        "name": "Filter replacement F7",
        "reason": "Mentioned 'F7 ersetzt' in the transcript.",
        "confidence": 10,
        "quantity": 1
      }
    ]
  },
  "gptResult":      { "plainResponse": "..." },
  "metaInformation": { "retries": 0, "appliedModel": "..." }
}

Step 4b: Identify materials

Same shape as 4a, with Materialkatalog as the catalogue parameter and a different schema/prompt.

curl -X POST "$XANDER_BASE_URL/prompt/informationextraction-structured" \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "UserQuery": { "UserQueryText": "" },
    "QueryParameter": [
      { "Name": "Materialkatalog",    "Value": "<reduced materials catalogue>" },
      { "Name": "Leistungsbeschrieb", "Value": "<the transcript from step 1>" }
    ],
    "Options": {
      "PromptIdentification": {
        "ProjectId": "your-project-id",
        "Promptname": "field-service/identify_materials"
      },
      "SessionMetadata": {
        "UseCaseName": "field-service-voice-intake",
        "UseCaseSessionId": "<same correlation id>"
      }
    },
    "OutputStructure": { "JsonSchemaDefinition": "{...same shape as services, replace 'services' with 'materials'...}" }
  }'

Step 4c: Identify tools

Same again, with the tools catalogue and a tools-specific schema. Three calls in parallel, the order they finish in does not matter.

Putting it all together: pseudo-code

const correlationId = ticket.id;

// Step 1
const transcript = await xander.transcribe(audioBlob, correlationId);

// Step 2
const summary = await xander.summarize(transcript, correlationId);

// Step 3: your own database
const reducedServices  = await db.servicesIn(summary.categories);
const reducedMaterials = await db.materialsIn(summary.categories);
const reducedTools     = await db.toolsIn(summary.categories);

// Step 4: parallel fan-out
const [services, materials, tools] = await Promise.all([
  xander.identifyServices(transcript, reducedServices, correlationId),
  xander.identifyMaterials(transcript, reducedMaterials, correlationId),
  xander.identifyTools(transcript, reducedTools, correlationId),
]);

await persistWorkReport({
  ticket,
  transcript,
  summary,
  services,
  materials,
  tools,
});

Gotchas and tips

  • Correlation ID is your friend. Always set SessionMetadata.UseCaseSessionId to the same value across all calls in one workflow. Otherwise debugging a failed workflow with our support team will be a needle-in-haystack exercise.
  • Catalogue format. The catalogue you pass in the QueryParameter can be JSON, CSV, plain text, TOON, or anything else the prompt template knows how to read. Coordinate with your xander contact when the prompt template is provisioned to make sure your catalogue format and the prompt agree.
  • Confidence scores are advisory. The confidence field in the schema is what the model claims about its own certainty. Use it to filter low-quality matches in your UI ("show only items with confidence ≥ 6"), not to make legal claims.
  • Don't skip the reduction in step 3. Even if your full catalogue has only ~500 entries, sending the full set on every call increases token consumption (and therefore your bill) and degrades match quality. Reduction is the most impactful single thing you can do for both quality and cost.
  • Parallel fan-out and concurrency limits. Step 4 fires three calls in parallel. That is well within typical concurrency limits, but if you batch many workflows simultaneously you may hit them. See Limits & Quotas for how the API tells you when you're approaching the limit.

Next

The Care Entry Workflow recipe walks through the same shape applied to a different domain (nursing-home documentation). If your use case is more about parsing structured documents than voice notes, see PDF Document Extraction.