# How do I use a voice agent for appointment confirmation, technician dispatch, and reject-reason capture?

Post-installation feedback and reject-reason triage for appliance service calls are already authored, shipping call-flow shapes in this codebase -- rating capture, technician-visit checks, and a reason-driven re-dispatch fork -- not a hypothetical. This page describes that shape and the /v1 calls that drive it, without claiming an outcome.


You trigger the call from your own dispatch or service system -- when a work order
closes, when a visit is scheduled, or when a job is marked rejected -- and the agent's
job is narrow and structured: confirm a fact, capture a rating, or capture a reason,
never hold an open-ended conversation. This is the use case with the most concrete
grounding on this site, because appliance-service call flows of exactly this shape
already exist as authored templates, not as a hypothetical drawn up for this page.

## The shape these flows actually have

Two authored call-flow templates in this codebase show the pattern:

- **Post-installation feedback.** After an AC installation or repair job closes, an
  outbound call asks three short questions -- service quality, technician behaviour,
  and whether the visit happened on time -- each captured as a 1-to-5 rating on its own
  step, with a shared rule that accepts a `1`-`5` digit and falls back to the reasoning
  layer for anything else. A customer who rates anything 3 or below gets thanked and
  told a callback is coming, rather than being pushed further.
- **Reject-call triage.** When a work order comes back with status "reject," the call
  first confirms it has reached the right person (self, a family member, a wrong
  number, or someone currently busy -- each routed differently), then asks whether a
  technician actually visited. From there it forks on *why* the job was rejected --
  bill unavailable at the time, payment was refused, a warranty concern, the product
  wasn't the right brand, or the customer wants to be called back later -- and routes
  either to a re-dispatch confirmation or a polite cancellation, depending on the
  reason captured.

Both are built from the same primitives every workflow on this platform uses: explicit
steps, a rule engine that matches keywords and patterns before ever asking the model to
decide, and named steps for whatever the call needs to capture -- bearing in mind that
those captured values are not published as a structured map on `/v1` (see step 3
below). Nothing here is a
claim about how these flows performed in a real deployment, what they converted at, or
how many calls ran through them -- it's a description of the flow shape, because that
shape is the honest, checkable evidence for this use case.

## The call sequence

1. Your dispatch or service-management system triggers the call when a work order hits
   the relevant state -- closed, scheduled, or rejected.

```bash
curl -X POST "$VARTA_BASE_URL/calls" \
  -H "Authorization: Bearer $VARTA_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: reject-triage-WO-55210" \
  -d '{
    "agent_id": "ag_412",
    "to": "+9198xxxxxxx",
    "customer_name": "Rahul",
    "context": {"work_order_id": "WO-55210", "technician_name": "Amit", "product": "split AC"}
  }'
```

> **Agent ids are numeric and studio-assigned.** `ag_412` above is the shape the API
> actually issues: `/v1` strips the `ag_` prefix and parses the rest as an integer
> (`backend/app/api/v1/_ids.py:56-61`), so an author-chosen slug like
> `ag_cod_confirm` is rejected as malformed before any lookup happens. Use the id the
> studio gave your agent.

   The `Idempotency-Key` is derived from the work order id, the same reasoning as the
   e-commerce pattern: a retry from your dispatch system's own webhook delivery must
   not place a second call about the same job. See [Place a call](/docs/api/calls) for
   the full contract.
2. Poll `GET /v1/calls/{call_id}` for status, or wait for your own end-of-call
   notification path.
3. Read back what was captured -- the rating, the reject reason, whether re-dispatch
   was confirmed. **Not from `state.slots`:** that field is published on every turn but
   is always `{}` on this build, and `GET /v1/sessions/{id}` returns no captured-slot
   key either. See [Sessions](/docs/api/sessions), under "`state.slots` is currently always
   empty", for the derivation.
   What you can use today is `state.step_progress` (which steps completed), the agent's
   own reply text on each turn, and the end-of-session `summary` when the instance
   produces one -- so recovering the rating and reject reason means parsing the
   conversation, not reading a slot map.

## What you still have to build

- **The dispatch-system trigger** -- a webhook or a poll against your field-service
  tool's own work-order state, and the mapping from a work order to the right
  `agent_id` and `context`.
- **Extracting the captured values at all, then writing them back.** The rating, the
  reject reason and the re-dispatch decision are spoken during the call, but `/v1`
  publishes no structured slot map carrying them (see step 3). Deriving them from the
  turn text and step progress is your integration to build, as is writing the result
  into your dispatch tool or CRM.
- **Actually re-dispatching the technician.** The call captures the customer's
  decision; scheduling a new visit with your field-service system is a separate action
  you take afterward, not something this call performs.
- **Deciding which jobs get a call at all**, and when. VARTA runs the conversation you
  authored; whether every closed job gets a feedback call, or only a sample, is a
  business rule you own.
