DMC-12 Mark Miller Subaru / dmc-12 guide
endpoint live · DMC-12 v0.6 · 11 tools

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 ↔ audit row 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);
  }
}
Retry strategy Retry only when 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_codeRetryableMeaning / when
VEHICLE_NOT_FOUNDnoVIN not in inventory.
VEHICLE_UNAVAILABLEnoVehicle status isn't available (sold / pending / reserved).
QUOTE_NOT_FOUNDnoUnknown quote_id.
QUOTE_EXPIREDnoQuote past its 30-minute TTL. Re-quote.
QUOTE_STATEnoQuote already converted or closed.
RESERVATION_NOT_FOUNDnoUnknown reservation_token.
RESERVATION_EXPIREDnoHold past its 30-minute TTL at lock time.
RESERVATION_STATEnoReservation not active (released / expired / converted).
RESERVATION_CONFLICTnoVIN already has an active hold. Pick another VIN.
RESERVATION_LIMITnoDaily reservation budget exhausted. Request a higher tier.
OFFER_NOT_FOUNDnoNegotiation — off at MM. Unknown offer.
OFFER_EXPIREDnoNegotiation — off at MM. Offer/thread lapsed.
OFFER_STATEnoNegotiation — off at MM. Offer not in an actionable state.
CONSENT_REQUIREDnoTaxonomy member; the schema-level customer_consent gate surfaces as VALIDATION_FAILED today.
CONSENT_INVALIDnoHand-off: supplied consent.expires_at is in the past. Refresh consent.
NOT_AUTHORIZEDnoMissing scope, or the resource is owned by another agent.
VALIDATION_FAILEDnoInput failed schema validation (VIN shape, E.164 phone, email, lengths, missing consent). Fix the payload.
RATE_LIMITEDyesRPM or daily budget exceeded. Back off and retry.
NOT_CONFIGUREDnoA required server-side config (e.g. hand-off destination, fee schedule) is unset.
UPSTREAM_UNAVAILABLEyesA dependency failed (e.g. SMTP send). Retry with backoff.
INTERNAL_ERRORyesUnexpected server fault. Retry; if it persists, email with error_id + trace_id.
Auth failures are different A bad/expired/wrong-audience bearer token returns JSON-RPC error -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.

copied