Partner onboarding
Partner access is granted, not self-served. You email us, we provision an Auth0 machine-to-machine client and seed your agent row, and you're live the same day. Here's the whole flow and the credential bundle you'll receive.
1. Email Ben
Onboarding starts with an email to
benr@mmsubaru.com
(or mm_reports@mmsubaru.com, subject [mm-inv-mcp onboarding] <your org>).
Include your legal entity, a named technical contact, your use case, and the scopes you need. Triage takes
about 10 minutes; total turnaround from triage to live is typically under an hour.
2. The credential bundle
We deliver these values over a secure channel (1Password time-bounded share or Bitwarden Send — never plain email or Slack).
| Field | Value |
|---|---|
| Auth0 tenant | mmbrain-prod.us.auth0.com |
| Token endpoint | https://mmbrain-prod.us.auth0.com/oauth/token |
| Grant type | client_credentials |
| Audience | mm-inventory-mcp |
| Signing alg | RS256 |
| Token TTL | 3600 s (1 hour) |
| client_id / client_secret | unique per partner — delivered securely |
Your sub at the service | <client_id>@clients |
| A2A endpoint | /a2a/ |
mmbrain-prod.us.auth0.com/.well-known/jwks.json) verifies the
access tokens Auth0 issues. The service JWKS
(/.well-known/jwks.json)
verifies the Agent Card signature. You consume both, for different things.
3. Mint a token
A2A_BEARER=$(curl -s -X POST https://mmbrain-prod.us.auth0.com/oauth/token \
-H 'content-type: application/json' \
-d '{
"client_id": "<your client_id>",
"client_secret": "<your client_secret>",
"audience": "mm-inventory-mcp",
"grant_type": "client_credentials"
}' | jq -r .access_token)
Auth0 returns a standard client-credentials token response:
{
"access_token": "eyJhbGciOiJSUzI1NiIsImtpZCI6…", // the RS256 JWT — send as Bearer
"expires_in": 3600, // seconds; re-mint before this elapses
"token_type": "Bearer",
"scope": "inventory:read quote:write reservation:write deal:handoff pricing:read"
}
| Field | Type | Description |
|---|---|---|
| access_token | string | The RS256 JWT. Send as Authorization: Bearer <access_token> on every /a2a/ call. |
| expires_in | int | Lifetime in seconds (3600). Cache and re-mint before it lapses. |
| token_type | string | Always Bearer. |
| scope | string | Space-delimited granted scopes. Your effective set at call time is this ∩ your agent row (see §5). |
Cache the token ~55 minutes and re-mint before expiry. Minting on every request will get you rate-limited at Auth0.
4. Call a tool
curl -X POST …/a2a/ \
-H "Authorization: Bearer $A2A_BEARER" \
-H 'content-type: application/json' \
-d '{
"jsonrpc": "2.0", "id": 1, "method": "tools/call",
"params": { "name": "search_inventory",
"arguments": { "query": "2024 Outback AWD under 35k", "limit": 5 } }
}'
The dry-run that completes onboarding is exactly this call: you mint a token and hit search_inventory; we confirm your trace_id in the audit log and flip your agent row to active = true.
5. The scopes we issue you
The default partner bundle is five scopes. We grant the minimum that covers your use case; tell us during triage if you need less.
| Scope | Unlocks | PII? |
|---|---|---|
| inventory:read | search_inventory, get_vehicle_by_vin, list_inventory, check_availability | no |
| quote:write | request_quote | no (opaque buyer_ref) |
| reservation:write | create_reservation, release_reservation, get_reservation_status | no (opaque customer_ref) |
| deal:handoff | initiate_deal_handoff | yes — name, phone, email |
| pricing:read | get_pricing_disclosure (+ OTD on quotes) | no |
inventory:read
cannot reserve, even if your row grants reservation:write. Mint tokens with exactly the scopes
the session needs. An empty intersection returns NOT_AUTHORIZED.
6. Rate limits & audit
| Budget | Default | Ceiling (on request) |
|---|---|---|
| Requests / minute (burst) | 60 | 600 |
| Reservations / day | 5 | 100 |
| Audit retention | 90 days | 365 days (with agreement) |
Rate-limit denials come back in the envelope as RATE_LIMITED (retryable), not as a
JSON-RPC error. Every response carries _metadata.trace_id — log it; it is the
only way we can correlate a complaint against the 90-day audit trail. Quote your trace_id and
any error_id in incident email.
RATE_LIMITED as retryable: exponential backoff with jitter (e.g. ~1s, 2s,
4s … capped, plus a random fraction) and a sane max-attempt ceiling — don't hot-loop. Stay under your
per-minute burst rather than relying on retries. Note the reservation budget is separate from the rate limit:
create_reservation charges your daily reservation budget, and a
same-UTC-day release_reservation refunds that slot — so a held-then-released VIN
within the day costs nothing against the budget. See the error taxonomy for which
codes are retryable.
7. Lifecycle
- Rotate (planned): mint a new secret in Auth0's dual-credential window, send us the new SHA-256 fingerprint; we update your row with no downtime.
- Revoke (compromise): email us; we run
seed_agent.py --revoke <sub>— effective within seconds. - Deactivate (end of agreement): we set
active = false; existing tokens are cleanly rejected at the middleware.