Deal hand-off
initiate_deal_handoff · scope
deal:handoff · the
one tool that accepts customer PII. It converts an active reservation into an emailed
deal-intent package for a live sales manager. PII is hashed in every audit/DB surface; plaintext flows only
to the dealer's inbox. Requires explicit customer_consent: true.
deal_handoffs table. The only place their plaintext lands is the outbound email to the
dealer desk (and the DriveCentric CRM lead, when enabled). Structured trade-in fields and consent
channels/expiry are plaintext — they're not PII.
Build a request
Toggle optional fields on and the request builds live below. Validation mirrors the server's Pydantic models, so a green banner means the call would be accepted as shaped.
true — the validator rejects false (PII transfer requires explicit consent)
$DMC12_TOKEN in the curl tab is a literal placeholder — swap in the bearer token you mint per
Partner onboarding. The endpoint origin is injected from the shared
MCP_BASE.
Full field reference
| Field | Type | Constraint · PII | |
|---|---|---|---|
| reservation_token | string | req | 10–80 chars |
| customer_contact.name | string | req | 1–120 · 🔒 |
| customer_contact.phone | string | req | E.164, 8–20 · 🔒 |
| customer_contact.email | string | req | 5–320 · 🔒 |
| customer_consent | bool | req | const true |
| financing_preference | enum | opt | cash|finance|lease|unknown (def unknown) |
| notes | string | opt | ≤500 · 🔒 |
| trade_in_disclosed | bool | opt | auto-true if any trade_in field set |
| trade_in | object | opt | extra fields forbidden |
| trade_in.description | string | opt | ≤500 · 🔒 |
| trade_in.appraised_value_partner | Money | opt | {amount≥0, currency ^[A-Z]{3}$} · 💲 |
| trade_in.vin | string | opt | ^[A-HJ-NPR-Z0-9]{17}$ |
| trade_in.year | int | opt | 1900–2100 |
| trade_in.make | string | opt | ≤40 |
| trade_in.model | string | opt | ≤80 |
| trade_in.mileage | int | opt | 0–1,000,000 |
| trade_in.condition | enum | opt | excellent|good|fair|poor |
| consent | object | opt | extra fields forbidden |
| consent.allowed_channels | enum[] | opt | ≥1 unique of email|phone|sms · advisory |
| consent.expires_at | datetime | opt | tz-aware · enforced (CONSENT_INVALID if past) |
| consent.consent_text | string | opt | ≤2000 · 🔒 |
Worked examples
Minimal — bare boolean consent
{
"reservation_token": "rsv_Qh7m…",
"customer_contact": { "name": "Jane Doe", "phone": "+15555551234", "email": "jane@example.com" },
"financing_preference": "unknown",
"trade_in_disclosed": false,
"customer_consent": true
}
+ trade_in
{
"reservation_token": "rsv_Qh7m…",
"customer_contact": { "name": "Jane Doe", "phone": "+15555551234", "email": "jane@example.com" },
"customer_consent": true,
"financing_preference": "finance",
"trade_in_disclosed": true,
"trade_in": {
"description": "Single owner, clean Carfax",
"appraised_value_partner": { "amount": 18500, "currency": "USD" },
"vin": "JF2GTACC4K8200000", "year": 2019, "make": "Subaru",
"model": "Crosstrek", "mileage": 48000, "condition": "good"
}
}
+ consent (channel-scoped)
{
"reservation_token": "rsv_Qh7m…",
"customer_contact": { "name": "Jane Doe", "phone": "+15555551234", "email": "jane@example.com" },
"financing_preference": "unknown",
"trade_in_disclosed": false,
"customer_consent": true,
"consent": {
"allowed_channels": ["email", "phone"],
"expires_at": "2026-06-01T00:00:00Z",
"consent_text": "Customer authorized follow-up by email and phone on 2026-05-27."
}
}
Full — everything populated
Use the builder above and toggle every optional field on; the params tab renders the full request.
What the dealer receives
On success the tool emails a deal-intent package to the dealer desk and returns a masked receipt — no plaintext customer data in the response:
{ "data": {
"handoff_token": "hnd_xyz…",
"sent_to_masked": "be****@markmillersubaru.com, ch****@markmillersubaru.com",
"sent_at": "2026-05-27T18:22:01+00:00",
"status": "sent",
"crm_status": "sent", // "sent" | "failed" | "disabled"
"crm_lead_id": "hnd_xyz…",
"next_steps": "A sales manager will contact ja****@example.com within 1 business day…"
},
"_metadata": { "trace_id": "trc_…", "tool": "initiate_deal_handoff", "protocol": "a2a" }
}
Response fields
The masked receipt. No plaintext customer data is ever returned. See Data formats for timestamp / enum conventions.
| Field | Type | Description | |
|---|---|---|---|
| handoff_token | string | always | Opaque receipt handle for the recorded hand-off. Do not parse. |
| sent_to_masked | string | always | Comma-separated, masked dealer-desk recipients (e.g. be****@…) — confirms where the package went without exposing addresses. |
| sent_at | string | always | ISO-8601 UTC — when the dealer-ops email sent (the moment the hand-off became real). |
| status | enum | always | sent on success. The email is the contract — a send failure returns an error, not a status. |
| crm_status | enum | always | sent | failed | disabled. Best-effort DriveCentric (ADF 1.0) lead outcome; disabled means the CRM sink is off. A failed CRM does not fail the call. |
| crm_lead_id | string | null | optional | CRM lead identifier when a lead was created; null/absent when crm_status is failed or disabled. |
| next_steps | string | always | Human-readable follow-up summary, with the customer email masked. |
- Email is the contract. The hand-off is recorded only after the dealer-ops email sends; an SMTP failure returns
UPSTREAM_UNAVAILABLE(email_send_failed) and persists nothing. - CRM is best-effort. A DriveCentric lead (ADF 1.0 XML) is created when enabled; failure is logged as
crm_status: "failed"but the call still succeeds.crm_status: "disabled"means the CRM sink is off. - crm_status outcomes:
sent·failed·disabled. - Not a sale. The reservation is still a soft hold; a human negotiates itemization (tax, doc, trade) and runs the deal through the DMS. There is no status-update tool — transitions happen dealer-side.
active and owned by your agent. Common errors:
RESERVATION_NOT_FOUND, RESERVATION_STATE, RESERVATION_EXPIRED,
NOT_AUTHORIZED (token owned by another agent), CONSENT_INVALID (supplied
consent.expires_at in the past). Full list on the error taxonomy page.