Error taxonomy
As of DMC-12 v0.3 every error payload carries a stable error_code, a retryable
flag, and a correlation error_id — layered over the legacy error string,
which is preserved byte-identically. Switch on error_code, not the human-readable
message.
The envelope
Errors come back inside the normal envelope (HTTP 200 on the A2A rail, except auth failures which are JSON-RPC -32001 / HTTP 401). The error fields live under data:
{ "data": {
"error": "reservation_expired", // legacy string — preserved
"message": "…", // human-readable; do NOT parse
"error_code": "RESERVATION_EXPIRED", // stable UPPER_SNAKE — branch on this
"retryable": false, // true only for transient classes
"error_id": "err_3f9a2c…" // quote this in incident email
},
"_metadata": { "trace_id": "trc_…", "tool": "initiate_deal_handoff", "protocol": "a2a" }
}
error_id correlates 1:1 to an audit_log row; _metadata.trace_id
correlates the whole call. Include both in any incident email to mm_reports@mmsubaru.com.
Recommended client branching
const d = resp.result.data;
if (d.error_code) {
switch (d.error_code) {
case "RATE_LIMITED":
case "UPSTREAM_UNAVAILABLE":
case "INTERNAL_ERROR":
return backoffAndRetry(d); // retryable === true
case "QUOTE_EXPIRED":
case "RESERVATION_EXPIRED":
return restartFromQuote(); // re-quote, re-reserve
case "RESERVATION_CONFLICT":
return pickAnotherVin(); // VIN already held
case "VALIDATION_FAILED":
return fixRequestShape(d); // your payload; do not retry as-is
case "NOT_AUTHORIZED":
return checkScopesAndOwnership(d); // scope intersection / token owner
case "CONSENT_INVALID":
return refreshConsent(); // hand-off: expires_at lapsed
default:
return surfaceToOperator(d);
}
}
retryable === true (RATE_LIMITED,
UPSTREAM_UNAVAILABLE, INTERNAL_ERROR). Use exponential backoff with
jitter (~1s → 2s → 4s, capped) and a hard max-attempt ceiling (3–5); after that,
surface to an operator with the error_id + trace_id. Everything else is terminal —
fix the cause (re-quote, pick another VIN, repair the payload, refresh consent) rather than retrying as-is.
The full code set (21)
Source of truth: schemas/error.json at mm-open/dmc-12. retryable is true only for the three transient classes.
| error_code | Retryable | Meaning / when |
|---|---|---|
| VEHICLE_NOT_FOUND | no | VIN not in inventory. |
| VEHICLE_UNAVAILABLE | no | Vehicle status isn't available (sold / pending / reserved). |
| QUOTE_NOT_FOUND | no | Unknown quote_id. |
| QUOTE_EXPIRED | no | Quote past its 30-minute TTL. Re-quote. |
| QUOTE_STATE | no | Quote already converted or closed. |
| RESERVATION_NOT_FOUND | no | Unknown reservation_token. |
| RESERVATION_EXPIRED | no | Hold past its 30-minute TTL at lock time. |
| RESERVATION_STATE | no | Reservation not active (released / expired / converted). |
| RESERVATION_CONFLICT | no | VIN already has an active hold. Pick another VIN. |
| RESERVATION_LIMIT | no | Daily reservation budget exhausted. Request a higher tier. |
| OFFER_NOT_FOUND | no | Negotiation — off at MM. Unknown offer. |
| OFFER_EXPIRED | no | Negotiation — off at MM. Offer/thread lapsed. |
| OFFER_STATE | no | Negotiation — off at MM. Offer not in an actionable state. |
| CONSENT_REQUIRED | no | Taxonomy member; the schema-level customer_consent gate surfaces as VALIDATION_FAILED today. |
| CONSENT_INVALID | no | Hand-off: supplied consent.expires_at is in the past. Refresh consent. |
| NOT_AUTHORIZED | no | Missing scope, or the resource is owned by another agent. |
| VALIDATION_FAILED | no | Input failed schema validation (VIN shape, E.164 phone, email, lengths, missing consent). Fix the payload. |
| RATE_LIMITED | yes | RPM or daily budget exceeded. Back off and retry. |
| NOT_CONFIGURED | no | A required server-side config (e.g. hand-off destination, fee schedule) is unset. |
| UPSTREAM_UNAVAILABLE | yes | A dependency failed (e.g. SMTP send). Retry with backoff. |
| INTERNAL_ERROR | yes | Unexpected server fault. Retry; if it persists, email with error_id + trace_id. |
-32001 with HTTP 401 — not the
data.error envelope. An empty scope intersection, by contrast, runs the dispatcher and returns
NOT_AUTHORIZED inside the envelope. See Partner onboarding §auth.
PII-safe by design
Validation-error messages you receive echo your own input so you can debug, but the server's audit summary records only the field location + error type — never the offending value. Customer contact, notes, and consent text are hashed before they touch any persistent store. See Deal hand-off for the PII model.