# Recipes

Apply the [authorization policy](../SKILL.md#authorization-and-working-style) to every step,
including local files, sends, paid features and test calls. Proceed within existing authority;
ask only for missing decisions or authority, or report blocked steps when unattended.

Each recipe follows discover → plan → apply → verify. Replace the UPPERCASE placeholders with
IDs returned by earlier steps (each `create` prints the new document, including `id`). JSON
goes in through `--data -` with a heredoc. Status words: *verified* = tested live on Rinzo;
*untested* = valid per Rinzo's schema but not yet proven by a live call. Test only within
authority; report unperformed live checks as pending, not proven by simulation.

Before any recipe:
```sh
rinzo whoami && rinzo account show       # account name, realm, timezone, caller_id
rinzo callflows list                     # existing extensions and numbers (avoid collisions)
rinzo numbers list                       # the account's numbers and what each routes to
```

---

For an existing PBX or SIP application, use the separate [trunk workflow](trunks.md).
The employee and hosted-routing recipes below are only needed for the hosted part of an account.

## 1. New employee: extension, voicemail, desk phone, softphone (*verified*)

Use the supplied name, email, extension (unused, for example 1001, or `auto`) and phones.
Resolve missing details under the authorization policy; do not require another planning round.

```sh
rinzo users create --first-name Jane --last-name Doe --email jane@example.com --extension auto --invite
#   → user + voicemail box + extension callflow (rings her phones, then voicemail), as SmartPBX
#     makes them; --invite emails Jane a link to set her password and sign in
rinzo devices create --name "Jane desk phone" --owner USER_ID --credentials-file jane-desk.json
#   → the SIP credentials go to a file only you can read; the output leaves the password out
rinzo devices create --name "Jane cell" --owner USER_ID --type cellphone --number +16045551234   # optional
```

- The output has `extension`, `voicemail_box_id`, `callflow_id` and, for each SIP device, a `sip`
  block (username, password, `sip_domain`, `outbound_proxy`) to configure the phone with
  (devices.md); with `--credentials-file` the password is only in that file.
  `rinzo devices credentials DEVICE_ID [--credentials-file PATH]` gives them again later.
- **Browser softphone**: once Jane has signed in at https://app.rinzo.ai (from the invitation),
  she opens Rinzo Softphone, which creates its own device. Never set or send passwords yourself;
  `rinzo users invite USER_ID` resends the invitation.
- **Desktop/mobile SIP app** (Zoiper, MicroSIP, …): `--type softphone`.
- **Encrypted media**: `--srtp` makes the device require SRTP (the phone must use TLS + SRTP).
- Make her an admin only within authority covering that privilege change (admins manage the whole account; other users
  only use their phones and their own profile): `--admin`, or later
  `rinzo users update USER_ID --set priv_level=admin`.

Verify: `rinzo devices status` (registered after the phone is set up). Within test-call authority,
dial her extension and leave a voicemail; check `rinzo calls list --since 1h`. Report pending checks.

---

## 2. Main number → auto attendant, business hours, holidays, lunch (*verified*)

Goal: during open hours callers hear "Press 1 for bookings, 2 for billing, 0 for reception";
at lunch and on holidays a message plays and they leave a voicemail; otherwise a closed menu.
`rinzo auto-attendant create` builds all of it from one spec: greetings, time rules, rule sets,
menus and the callflow, checked before anything is written (`--dry-run` writes nothing) and
rolled back if a write fails. It ends by simulating calls at open, closed, lunch and holiday times.

**a. Plan using supplied decisions; resolve only gaps**: the business's time zone (`rinzo account show` → `timezone` is the
account's), hours, breaks, holidays, what each key does, where unanswered calls go, which
number(s). Make the destinations first: users (recipe 1), ring groups (recipe 3), shared boxes
(recipe 5). Use WAV/MP3 recordings, or create TTS media (see callflows.md) and use `media:MEDIA_ID` as the greeting.

**b. Write the spec** (greeting files are relative to the spec file):
```sh
cat > main-number.json <<'EOF'
{
  "name": "Main number",
  "timezone": "America/Vancouver",
  "extension": "auto",
  "greetings": {"main": "main-menu.wav", "closed": "closed.wav", "lunch": "lunch.wav", "hours": "hours.wav",
                "holiday": "holiday.wav"},
  "hours": {"mon-fri": "08:00-18:00", "sat": "09:00-13:00"},
  "breaks": {"mon-fri": "12:00-13:00"},
  "holidays": ["01-01", "12-25", "12-26", "first monday of september", "good friday",
               {"date": "monday before may 25", "name": "Victoria Day"},
               {"date": "2026-11-11", "name": "Remembrance Day"}],
  "open": {"menu": {"greeting": "main",
                    "keys": {"1": "ring-group:BOOKINGS_GROUP_ID", "2": "ring-group:BILLING_GROUP_ID",
                             "4": {"play": "hours", "then": "restart"},
                             "0": "ring-group:RECEPTION_GROUP_ID"},
                    "dial_extensions": ["1001-1009"],
                    "no_key": "ring-group:RECEPTION_GROUP_ID",
                    "otherwise": "ring-group:RECEPTION_GROUP_ID"}},
  "closed": {"menu": {"greeting": "closed",
                      "keys": {"1": "voicemail:BOOKINGS_BOX_ID", "2": "voicemail:BILLING_BOX_ID"},
                      "no_key": "voicemail:BOOKINGS_BOX_ID"}},
  "break": {"play": "lunch", "then": "voicemail:BOOKINGS_BOX_ID"},
  "holiday": {"play": "holiday", "then": "closed"}
}
EOF
rinzo auto-attendant create --spec main-number.json --dry-run     # checks it all, writes nothing
rinzo auto-attendant create --spec main-number.json               # → callflow_id, extension, check
```
- Order: holiday, then break, then open hours, otherwise closed. `holiday` and `break` default to
  `closed`. Windows include both ends (12:00–13:00: 13:00:00 is still lunch).
- Holidays: `"12-25"` (every year), `"2026-11-11"` (once), `"first monday of september"`
  (`first`…`fifth`, `last`), each optionally `{"date": …, "name": …}`. A holiday is the whole day.
  Rinzo cannot repeat `"good friday"`, `"easter sunday"`, `"easter monday"` or
  `"WEEKDAY [on or] before|after MONTH DAY"` (Victoria Day: `"monday before may 25"`) by itself, so
  they are written as dates for the next 10 years; the output's `holidays_note` says until when,
  and `update` with the same spec extends them. Anything else that moves (an observed Monday,
  US Election Day): give its dates.
- Menus: `plays` (default 2) is how many times the greeting plays in total; `no_key` runs at the
  first silence; `otherwise` runs when the plays run out (wrong keys use plays up); without it
  Rinzo says goodbye. `timeout` (seconds, default 10) waits for a key. `"dial_extensions": true`
  lets callers dial **any** extension on the account (helper extensions too);
  `["1001-1009", "2001", "20XX"]` allows only those.
- `"closed"` (or `"open"`, `"holiday"`, `"break"`) as an action means "whatever that part does": the
  holiday above plays its greeting, then gives the after-hours menu, which is written once.
- `number:+1…` as an action is a plain outbound call that ends if nobody answers; for an emergency
  or on-call key that should fall back to voicemail, point it at a ring group with a `number:`
  member (recipe 3).
- Actions: a target (below), `"restart"` (back to the start: the time check, then the menu again;
  key 4 above plays the opening hours, then restarts), `{"play": GREETING, "then": ACTION}`, or
  `{"menu": {…}}`, nested as deep as needed. `restart` is allowed only after a key press, so a
  silent caller cannot go round forever. A GREETING is a `greetings` name, `media:ID` or `system_media/…`.
- `extension` gives staff a number to test on before any outside caller hits it; `numbers` routes
  the account's numbers straight away (they must not be on another callflow).

**c. Verify.** Read the `check` block of the output (one simulated call at a time that really
falls in each part of the schedule: open, closed, break, holiday), then walk the paths the person
cares about:
```sh
rinzo callflows simulate EXTENSION --at "2026-12-25 10:00"            # a holiday
rinzo callflows simulate EXTENSION --at "2026-10-14 10:00" --keys 2   # open, presses 2
rinzo callflows simulate EXTENSION --at "2026-10-14 21:00" --keys 9,9 # closed, two wrong keys
rinzo callflows simulate EXTENSION --at "2026-10-14 07:59" --at "2026-10-14 08:00" \
  --at "2026-10-17 14:01" --keys 1 --keys 4,3 --keys silence          # 9 runs: edges x keys
```
Within test-call authority, check the extension in and out of hours; otherwise report that live check pending.

**d. Move the real number within routing authority** (retain its old destination; execute
back-to-back, because the number rings nothing in between):
```sh
rinzo numbers unassign +16045550123      # usually it was on the owner's extension callflow
rinzo numbers assign +16045550123 --callflow MAIN_CALLFLOW_ID
rinzo numbers list                       # confirm the route
```

**Changing it later**: `rinzo auto-attendant show ID` prints the spec it was built from (greetings
as `media:ID`, `extension` as the number it got). Save its `spec`, edit it, then
`rinzo auto-attendant update ID --spec FILE` (unchanged, it is accepted as shown): it rebuilds in place (same callflow id and numbers
unless the spec gives `extension`/`numbers`) and deletes what it made before, keeping anything the
new spec still uses. `rinzo auto-attendant delete ID --yes` removes it with everything it made.
"Closed today" without a rebuild: `rinzo rules update RULE_ID --set enabled:=true` on one of its
holiday rules, then `--unset enabled` (`enabled` forces a rule on or off whatever the time).

Building it by hand instead (the documents, for anything the spec cannot express): callflows.md.

---

## 3. Ring group / hunt group (*verified*)

Everyone at once on its own extension (first free from 2000), the way SmartPBX's Groups page
makes it (so it shows up there):
```sh
rinzo ring-groups create --name Sales --member USER_1 --member USER_2 --overflow voicemail:VMBOX_ID
#   → group_id, extension, callflow_id; members are user ids or device ids (device:ID)
rinzo ring-groups list                   # strategy, ring time, member ring times, overflow
rinzo callflows simulate EXTENSION       # who rings, for how long, where it ends
```
- Hunt (one after another, in order): `--strategy single` (`--timeout` is then per member).
- **A backup person** (ring Tom and Lena 20 s, then Priya 15 s, then voicemail):
  `--member TOM --member LENA --member PRIYA,delay=20,timeout=15 --timeout 20`. Each member rings
  from its `delay` for its `timeout`; the group rings until the last one stops (35 s here), as the
  Groups page shows it. Change one member's timing: `ring-groups update ID --add-member PRIYA,delay=25`.
- **An outside number as a member** (`--member number:+14035552671,timeout=30`): the group makes a
  cell-phone device that rings it (the person answering presses 1, so the cell's own voicemail can't
  take the call), and deletes it again when the member or the group is removed. This is how an
  **on-call line falls back to voicemail**:
  ```sh
  rinzo ring-groups create --name "On-call vet" --extension none \
    --member number:+14035552671,timeout=30 --overflow voicemail:BOX_ID
  # then an auto-attendant key: "1": "ring-group:GROUP_ID"
  ```
  (A plain `number:+1…` target instead ends the call when nobody answers.)
- **No extension of its own** (`--extension none`): for a group that only an overflow or an
  auto-attendant sends calls to, as `ring-group:GROUP_ID`. Its callflow gets a pattern no dialled
  number matches (`^no-number-GROUP_ID$`), because Rinzo needs a number or a pattern.
- `--overflow` is where unanswered calls go, any target: `voicemail:BOX_ID`, `ring-group:ID`
  (another group, then its overflow), `user:ID` (a person, then their voicemail),
  `callflow:ID`, `extension:N`, `number:+1…` (an outbound call, billed), `hangup`. Without it,
  unanswered callers hear "not available". (`--voicemail BOX_ID` still works.)
- A user member rings every phone that user has.
- Change it in place: `rinzo ring-groups update GROUP_ID` with `--add-member`, `--remove-member`,
  `--member` (replaces all), `--strategy`, `--timeout`, `--overflow TARGET|none`, `--name`,
  `--extension`. `rinzo ring-groups get GROUP_ID` reads one back.
- Give it an outside number: `rinzo numbers assign N --callflow CALLFLOW_ID`.
- Remove it: `rinzo ring-groups delete GROUP_ID --yes` (the group and both of its callflows).

Prefer `ring-groups create` over a hand-built `ring_group` callflow (callflows.md): the
Control panel's Groups page shows only groups made the SmartPBX way.

---

## 4. Forward a user's calls to a cell phone (*SmartPBX shape*)

```sh
rinzo users forward USER_ID --to +16045551234            # the cell rings instead of their phones
rinzo users forward USER_ID --to +16045551234 --with-phones  # the cell and their phones together
rinzo users forward USER_ID --to +16045551234 --failover # the cell, in place of a phone that is offline
rinzo users forward USER_ID --off
rinzo callflows simulate THEIR_EXTENSION                 # shows which of these applies now
```
- `--failover` is for a phone that is **not registered** (unplugged, no network): Rinzo dials the
  number in its place, also when the user is rung as part of a ring group. A phone that rings and
  is not answered does not fail over; the call goes on to voicemail.
- By default whoever answers the cell must press 1, which stops the cell's own voicemail from
  taking the call; `--no-keypress` connects straight away.
- `--direct-only` (plain forwarding): calls to the user are forwarded; a ring group they are in
  rings just their phones.
- The number can be an outside number (`+1…`, an outbound call) or one of the account's extensions.
- Another way to ring the desk phone and cell together: a `cellphone` device for the user (recipe 1).
- Forwarded calls leave as outbound calls: they use the plan's monthly minutes, then calling credit.

---

## 5. Shared voicemail box (for example "Support") (*verified for voicemail*)

```sh
rinzo voicemail create --name "Support" --notify support@example.com --extension auto
#   → box (mailbox: first free from 3000), a generated 6-digit pin, an extension that goes
#     straight to the box (the mailbox number when free), and target voicemail:BOX_ID
```
- **A shared box needs a PIN**: Rinzo lets only the owner into a box without one, so with no PIN
  and no owner nobody can listen to it by phone (`*97`, then mailbox and PIN). `voicemail create`
  generates one for a box without `--owner` (`--pin 482913` to choose, `--no-pin` to refuse).
  The PIN is printed once; `--pin-file PATH` writes it to a file only you can read instead (hand
  that to the person). Later reads hide it.
- `rinzo voicemail delete BOX_ID --yes` also deletes the extension that went only to it.
- `--notify` (repeatable, or comma-separated) emails each message; `--owner USER_ID` makes it a
  person's box; `--mailbox N` picks the number.
- Point unanswered calls at it: `rinzo ring-groups update GROUP_ID --overflow voicemail:BOX_ID`,
  or a menu key in an auto-attendant spec. Check it with `rinzo voicemail messages BOX_ID --folder new`.

---

## 6. Conference bridge (*verified*)

```sh
rinzo conferences create --name "Team bridge" --pin-file team-bridge.json
#   the conference, an extension (first free from 8000) and a 6-digit participant PIN,
#   saved in the file (readable only by you) instead of printed
rinzo conferences create --name "Board" --extension 8100 --pin 4321 --moderator-pin 9876
rinzo callflows simulate 8000            # "asks for a PIN ... then puts the caller in conference"
```
- Participants join able to talk (Rinzo's own default would mute them); `--join-muted` for a
  lecture-style call, with a `--moderator-pin` for whoever leads it.
- `--pin none` makes an open room (anyone who reaches the extension joins; simulate warns).
- `rinzo conferences delete ID --yes` also deletes its extension.

Optional outside access: `rinzo numbers assign N --callflow CALLFLOW_ID` (every participant's
inbound minutes count as calls: the plan's minutes, then billed).

---

## 7. Block a caller

Blocked callers get "603 Decline" before any callflow runs, so the block also protects calls
Kazoo can attribute to the account before routing (an AI receptionist never answers them, no
minutes are used). The list is the account's native blocklist (`blacklists`); `rinzo blocked`
keeps one list named "Blocked callers" and activates it on the account the first time.
```sh
rinzo blocked list                                             # every blocked number, and anonymous callers
rinzo blocked add +16045559999 --reason "spam: spoofed number"   # E.164; creates and activates the list once
rinzo blocked add +16045559999                                 # already blocked: reported as unchanged
rinzo blocked remove +16045559999 --yes                        # from whichever list holds it; the list stays
rinzo blocked anonymous --on                                   # callers who withhold their number (--off allows them)
```
`add` and `anonymous` need no confirmation beyond the authorization policy; `remove` needs
`--yes`. A number a receptionist flagged as suspect appears in `rinzo receptionists sessions ID`
with the command that blocks it (see [Spam screening](receptionists.md#spam-screening)). Other
lists already on the account keep working: `list` and `remove` cover all of them, `add` only
writes "Blocked callers". For a list with its own name or raw edits, `rinzo blacklists` and
`rinzo account update --set 'blacklists:=[...]'` remain (*untested*: the array is replaced, so
include the existing ids).

---

## 8. Record calls to a number (*verified*)

Rinzo keeps each recording for 90 days; the business listens or downloads it with
`rinzo recordings` or in the Control panel. Recording laws vary by place (one-party or two-party
consent): the notice prompt is on by default, and the business is responsible for its own notice
if it turns the prompt off. Tell the person so.
```sh
rinzo recording status                                # which numbers and people record now
rinzo recording enable --number +16045550100          # notice ("this call is recorded") then record, whatever answers
rinzo recording enable --number +16045550100 --language es   # the notice in Spanish (default: the account's language)
rinzo recording enable --number +16045550100 --no-notice     # no prompt: the business gives its own notice
rinzo callflows simulate +16045550100                 # the call now starts with play → record_call → the old flow
rinzo recordings list --since 7d                      # newest first: start, direction, from, to, duration
rinzo recordings get RECORDING_ID --out call.mp3
rinzo recording disable --number +16045550100         # removes exactly the two nodes enable added
```
`enable` is idempotent (`result: unchanged` when already on); `disable` refuses a callflow whose
top node is a `record_call` this CLI did not add. `--dry-run` shows the callflow it would save.

To record every call one person handles (inbound and outbound, between extensions and outside),
with no notice: `rinzo recording enable --user USER_ID` (sets the user's `call_recording`, the same
setting as SmartPBX's "Call recording" feature); `rinzo recordings list --user USER_ID` lists them.
Sending recordings to your own storage instead: set a `url` in `call_recording` (`rinzo users
update USER_ID --set 'call_recording.inbound.offnet.url=https://rec.example.com/upload'`, each
direction/source) or in a `record_call` node; Rinzo then uploads `call_recording_<call id>.mp3`
there and keeps no copy. AI receptionists: `retention.store_audio` in the spec (receptionists.md).

---

## 9. Send call activity to a CRM or other system

Push, on every AI receptionist call (signed; `--include-transcript` only when the CRM needs it):
```sh
openssl rand -hex 32 > hook-secret.txt && chmod 600 hook-secret.txt     # give the same key to the receiver
rinzo webhooks create --name "CRM: AI calls" --uri https://crm.example.com/hooks/rinzo --event ai_call \
    --secret-file hook-secret.txt
rinzo webhooks test HOOK_ID --secret-file hook-secret.txt              # the sample event, to the real URL
```
Push, on every finished call leg (human-answered calls too; de-duplicate on `call_id`):
```sh
rinzo webhooks create --name "CRM: calls" --uri https://crm.example.com/hooks/rinzo/calls --event channel_destroy
rinzo webhooks update HOOK_ID --set custom_request_headers.Authorization="Bearer SHARED_TOKEN_FROM_THE_PERSON"
```
Zapier or Make: a Catch Hook / Custom webhook URL as `--uri`, no secret needed, then `webhooks test`
so the trigger learns the fields. Pull, on a schedule (for example every 10 minutes):
`rinzo calls list --since 15m --json` and de-duplicate on `id`.
Payload fields, signature verification (Python and Node), retries and auto-disable are in
integrations.md. Within external-send authority, run `webhooks test` and check the CRM received it;
otherwise report the check pending. `rinzo webhooks attempts HOOK_ID` shows later deliveries.

---

## 10. Company caller ID and name

```sh
rinzo account update --set caller_id.external.number=+16045550123 --set caller_id.external.name="Acme Dental"
```
The number must be one of the account's numbers. Names longer than 15 characters are cut by
carriers.

---

## 11. Offboard an employee (destructive: apply the authorization policy)

1. Move any outside number off their extension: `rinzo numbers list`, then
   `numbers unassign` / `numbers assign` to its new destination.
2. Safest first: `rinzo users update USER_ID --set enabled:=false` (stops logins and ringing).
3. Only with deletion authority from the task or standing delegation (download voicemail first if authorized and wanted):
   `rinzo users delete USER_ID --yes` deletes the user with their extension callflow and voicemail
   box; their devices are kept but unassigned (`--delete-devices` deletes them too). The output
   lists every outside number that no longer rings anyone.

---

## 12. A plumbing company from a template (*untested*)

The [template workflow](receptionists.md#start-from-an-industry-template) in full. The business
gave: its name and area, office hours, that dispatch is the office ring group, a technician who takes
emergencies after hours, and its website.

```sh
rinzo receptionists templates                                        # confirm plumbing-heating and its variables
rinzo ring-groups list && rinzo users list && rinzo voicemail list   # the real ids for the destination variables
#   no office group or mailbox yet? recipes 3 and 5 first
rinzo receptionists template plumbing-heating --out ./northshore \
    --set business_name="Northshore Plumbing & Heating" --set service_area="Port Alder and the North Valley" \
    --set office_hours="Monday to Friday 7 AM to 6 PM, Saturday 8 AM to 2 PM" \
    --set dispatch=ring-group:GROUP_ID --set oncall=user:TECH_USER_ID --set office_voicemail=voicemail:BOX_ID \
    --set website=https://www.example.com
#   → receptionist.json, after-hours.json, knowledge/faq.md, knowledge/emergency.md, and the gaps:
#     every [FILL IN: …] (service call price, call-out price, quotes, work not offered, shut-off valve, gas utility line)
```
Read the four files. Put the business's answers in place of every marker (facts and documents); delete a
line they have no answer for. Do not fill a price or a policy you were not given. Then:
```sh
rinzo receptionists apply --spec ./northshore/receptionist.json --dry-run    # information_gaps should be empty now
rinzo receptionists apply --spec ./northshore/receptionist.json              # uploads the two documents, starts the website import
rinzo receptionists apply --spec ./northshore/after-hours.json               # reuses the same documents
rinzo receptionists publish ai-receptionist-dispatch --idempotency-key publish-dispatch-001        # within authorization
rinzo receptionists publish ai-receptionist-after-hours --idempotency-key publish-after-hours-001
rinzo receptionists attach ai-receptionist-dispatch --number 1000 --dry-run  # an extension to call first
```
Place them in the schedule only when cutover is authorized: the dispatch receptionist where the office
hours branch ends (for example as the office group's overflow) and the after-hours one on the closed
branch (`attach ai-receptionist-after-hours --callflow MAIN_CALLFLOW_ID --at /flow/children/KEY`), then
`rinzo callflows simulate NUMBER --at "2026-11-03 10:00" --at "2026-11-03 22:00" --ai-outcome human`.
A real call to each (a booking question, an emergency, a request for a person) is the test, within
authorization or by the person; `rinzo receptionists sessions ai-receptionist-dispatch` shows how it ended.

---

## 13. Order status lookup from the receptionist (*untested*)

The business wants callers to hear where their order is. It gave: a read-only HTTPS endpoint
(`GET https://api.example.com/orders/ORDER_NUMBER` answers `{"order": {"number", "status", "eta"}}`)
and an API key for it. Confirm first that the endpoint returns only what a caller may hear (status and
dates, not the address or payment details): anyone with an order number gets its answer. Details and
the rules are in [receptionists.md](receptionists.md#business-lookups-customer-tools).

Add to the receptionist's spec (the key goes in the spec file only; never into a transcript or git):
```json
"tools": [{"id": "order_status",
           "description": "Look up an order by its number and say its status and expected delivery date",
           "parameters": {"order_number": {"type": "string", "required": true,
                                           "description": "The order number the caller gives, digits only"}},
           "request": {"method": "GET", "url": "https://api.example.com/orders/{order_number}",
                       "headers": {"Authorization": "Bearer THE_KEY_THE_BUSINESS_GAVE"}},
           "response": {"speak": "Order {{order.number}} is {{order.status}}, expected {{order.eta}}.", "max_chars": 300},
           "timeout_ms": 2000}]
```
```sh
rinzo receptionists apply --spec receptionist.json --dry-run   # the proposed draft shows the header as **redacted**
rinzo receptionists apply --spec receptionist.json             # saves the draft; nothing is published
rinzo receptionists tools list ai-receptionist-front-desk      # the tool as the model sees it, host and header names
rinzo receptionists tools test ai-receptionist-front-desk order_status --arg order_number=42   # calls the real endpoint
#   → status ok and the sentence the receptionist would read; not_found / unavailable / error → it offers to take a message
rinzo receptionists publish ai-receptionist-front-desk --idempotency-key publish-front-desk-002   # within authorization
```
A test with a real order number (one the business supplies) and then a real call asking "where is my
order 42?" are the checks; report them pending when outside authority. If the endpoint answers in
more than two seconds, the receptionist says it is checking, then falls back to a message: put a
faster, thinner endpoint in front of the system of record rather than raising `timeout_ms`.
