DealerCRM Partner APIDeveloper Documentation

Partner API

Read from and write to a DealerCRM dealer's data — leads, customers, inventory, and activity — from your own application. This is the complete reference: authentication, every endpoint, webhooks, and errors.

The dealer creates an API key for you inside their DealerCRM admin and shares it securely. You send it as a Bearer token on every request. You never need a DealerCRM login yourself.

Getting started

Base URL

https://dealercrm.com/partner/v1
Always call the bare host dealercrm.com — not www.. The www redirect strips the Authorization header, so requests would fail auth.

Conventions

TopicDetail
FormatJSON in and out. Send Content-Type: application/json on writes.
Lists{ "object":"list", "items":[…], "next_cursor":<id|null>, "limit":N }. Page with ?limit= (max 100) and ?after=<last id>; stop when next_cursor is null.
IdempotencySend Idempotency-Key: <unique> on any POST. A repeat returns the original result instead of duplicating.
Rate limitsPer-minute limit + daily quota per key. Over → 429 with a Retry-After header (seconds).
Body sizeRequests over 256 KB → 413.
Timestampscreated / activity times are unix seconds; expires_at and webhook times are ISO 8601.

Authentication

Every request carries the API key the dealer created for you, as a Bearer token:

Authorization: Bearer dcp_live_xxxxxxxx.xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

X-Api-Key: <key> is accepted as an alternative. The key is shown only once when created — store it securely; if it's lost or exposed, the dealer rotates it (a rotate issues a new key and instantly disables the old one). Verify a key works with a single call:

curl https://dealercrm.com/partner/v1/ping \
  -H "Authorization: Bearer $DEALERCRM_KEY"

Scopes

A key can only reach the endpoints its scopes cover (the dealer chooses them per key). A call outside scope returns 403 insufficient_scope.

Read and write are independent per area. Each major area — Customers, Inventory, Activities — has a separate :read and :write scope, so a key can be granted read, write, both, or neither for each area, in any combination (e.g. a marketing tool that only logs emails gets activities:write alone; a listing site that only reads inventory gets inventory:read alone). The dealer picks these per key in the DealerCRM admin, and a key can never exceed what the dealer's subscription allows.

ScopeGrants
pingGET /ping
customers:readGET /customers, GET /customers/{id}
customers:writePOST /customers
inventory:readGET /inventory, GET /inventory/{id}
inventory:writePOST /inventory (status)
activities:readGET /activities
activities:writePOST /activities
leads:writePOST /leads
webhooks:managePOST / GET / DELETE /webhooks

Pull data (read)

GET/ping — any valid key

Returns the key's account, plan, and granted scopes. The best first call.

GET/customers — customers:read

Paginated. Query: limit (≤100), after (last customer_id).

curl "https://dealercrm.com/partner/v1/customers?limit=50" \
  -H "Authorization: Bearer $DEALERCRM_KEY"
{
  "object": "list",
  "items": [
    { "customer_id": 10000001, "first_name": "Jane", "last_name": "Doe",
      "full_name": "Jane Doe", "email": "jane@example.com",
      "phone_mobile": "3105551234", "do_not_contact": false,
      "preferred_contact_channel": "sms",
      "point_person": { "id": 42, "name": "Alex Rivera" },
      "customer_since": "2025-03-01", "created": 1740787200 }
  ],
  "next_cursor": 10000001,
  "limit": 50
}

GET/customers/{id} — customers:read

One customer, plus full phones[], emails[], and names[] arrays.

GET/inventory — inventory:read

Query: limit, after (last inventory_id), status = active (default), sold, or all.

curl "https://dealercrm.com/partner/v1/inventory?status=active&limit=25" \
  -H "Authorization: Bearer $DEALERCRM_KEY"
{ "object":"list", "items":[
  { "inventory_id": 139450, "year": 2024, "make": "Ford", "model": "F-150",
    "trim": "XLT", "vin": "1FTFW1E86PKE00000", "stock_number": "A1234",
    "condition": "new", "mileage": 12, "ext_color": "Oxford White",
    "msrp": 58230, "asking_price": 55990, "internet_price": 54990,
    "sale_status_type": 1, "primary_image_url": "https://…",
    "updated": "2026-07-09T16:15:49Z" }
], "next_cursor": 139450, "limit": 25, "status": "active" }

GET/inventory/{id} — inventory:read

One unit by inventory_id.

GET/activities — activities:read

Requires ?customer_id= (the API never dumps a whole tenant). Newest first; limit ≤100.

curl "https://dealercrm.com/partner/v1/activities?customer_id=10000001&limit=20" \
  -H "Authorization: Bearer $DEALERCRM_KEY"

Push data (write)

All writes accept an Idempotency-Key header. Unknown fields, a bad VIN/email/phone, or an out-of-range value return 422 validation_failed with a per-field details array — nothing is written on a validation failure.

POST/leads — leads:write

Submits a lead through the same pipeline as native leads (duplicate detection, enrichment, owned-vehicle attach, rep routing). Requires at least one of customer.email / phone / full.

curl -X POST https://dealercrm.com/partner/v1/leads \
  -H "Authorization: Bearer $DEALERCRM_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: acme-lead-9931" \
  -d '{
    "customer": { "full": "Jane Doe", "email": "jane@example.com", "phone": "310-555-1234" },
    "vehicle":  { "year": 2024, "make": "Ford", "model": "F-150", "vin": "1FTFW1E86PKE00000" },
    "trade":    { "year": 2019, "make": "Toyota", "model": "Camry" },
    "intent": "sales",
    "source": "Acme Marketing",
    "consent": { "sms_optin": true },
    "store_account_id": 1
  }'
{ "object":"lead", "lead_id": 812, "customer_id": 10000042,
  "activity_id": 48500441, "dedup_method": "new_customer",
  "intent": "sales", "is_existing_customer": false }

POST/customers — customers:write

Creates or matches a customer (duplicate detection runs first; on a match it enriches instead of duplicating). Fields: first_name, last_name, full_name, email, phone, owned_vin, store_account_id. At least one identifier required.

curl -X POST https://dealercrm.com/partner/v1/customers \
  -H "Authorization: Bearer $DEALERCRM_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "full_name": "Jane Doe", "email": "jane@example.com", "phone": "3105551234" }'
{ "object":"customer", "customer_id": 10000042, "created": true, "dedup_method": null }

POST/activities — activities:write

Logs an interaction on an existing customer (resolve by customer_id, or by email/phone; it never creates a customer — use /leads or /customers for that). This one endpoint represents almost any "X happened with this customer" — calls, texts, emails, notes — with rich, typed fields. The full JSON you send is also stored verbatim for audit.

FieldApplies toNotes
kind (req)allcomms: note, call_inbound, call_outbound, text_inbound, text_outbound, email_inbound, email_outbound · first-class typed events: appraisal, trade_in, credit_app, service_ro, appointment, test_drive, meeting, write_up, quote, delivery, equity_alert (see the table below the examples)
customer_id / email / phoneallat least one — resolves the existing customer
bodyallmessage / summary / notes (≤20k)
occurred_atallISO time it happened (defaults to now)
employee_nameallrep/agent involved
duration_secondscallscall length
recording_urlcallslink to the recording (shown on the timeline)
transcriptcallsfull transcript (≤100k; becomes the body)
from / tocalls, email, textendpoints (numbers / addresses)
outcomecalls, emailcalls: connected/voicemail/no_answer/missed/busy · email: sent/delivered/opened/clicked/bounced/unsubscribed
subjectemailemail subject
campaignemail, marketingcampaign label (also becomes the lead source)
statusemail, text, credit_app, service_rofree-form status label (delivery status, credit decision, RO status)
amountappraisal, trade_in, credit_app, service_ro, quote, write_up, equity_alertmoney — meaning is per-kind: appraisal offer, trade allowance, RO customer-pay, quote price, equity position
referencetyped eventsyour external number (appraisal id, RO #, quote #)
ro_numberservice_rothe repair-order number
lendercredit_applender / bank name
scheduled_atappointmentISO time of the appointment (defaults to occurred_at)
appointment_typeappointmentmeeting (default) · delivery · service · contact · other
appointment_statusappointmentscheduled · confirmed · completed · no_show · canceled · rescheduled
vehicleall{ year, make, model, vin, stock } — the unit discussed / appraised / traded
metaallany extra JSON (≤6k) — stored verbatim (scores, sentiment, IDs, extra amounts…)

Example — a call-tracking tool pushes a recorded, transcribed call

curl -X POST https://dealercrm.com/partner/v1/activities \
  -H "Authorization: Bearer $DEALERCRM_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: calltrack-8891" \
  -d '{
    "customer_id": 10000042,
    "kind": "call_inbound",
    "duration_seconds": 214,
    "outcome": "connected",
    "from": "+13105551234", "to": "+13235550100",
    "recording_url": "https://calls.example.com/rec/8891.mp3",
    "transcript": "Customer: Is the F-150 still available?\nAgent: Yes — want to come see it Saturday?",
    "employee_name": "Alex Rivera",
    "occurred_at": "2026-07-09T18:30:00Z",
    "meta": { "call_score": 92, "sentiment": "positive" }
  }'

Example — a marketing platform reports an email it sent

curl -X POST https://dealercrm.com/partner/v1/activities \
  -H "Authorization: Bearer $DEALERCRM_KEY" -H "Content-Type: application/json" \
  -d '{ "email": "jane@example.com", "kind": "email_outbound",
        "subject": "Your F-150 is waiting", "campaign": "July Truck Event",
        "outcome": "sent", "body": "Hi Jane — the truck you looked at is still here." }'

Response: { "object":"activity", "activity_id": 48567841, "customer_id": 10000042, "kind": "call_inbound" }

First-class typed events

Beyond comms, kind also accepts real dealership events. Each lands as its own labeled card on the customer's profile timeline (an Appraisal, a Trade-In, a Credit App, a Repair Order, an Appt Scheduled) — never a generic note — and appointments additionally count as scheduled appointments. Use the typed fields (amount, status, lender, ro_number, scheduled_at, appointment_status, vehicle) and add anything else in meta.

kindShows asKey fields
appraisalAppraisalamount (offer), vehicle, reference, body
trade_inTrade-Inamount (allowance), vehicle, body
credit_appCredit Appstatus (decision), lender, amount, body
service_roRepair Orderro_number, status, amount (customer pay), vehicle
appointmentAppt Scheduledscheduled_at, appointment_type, appointment_status, vehicle
test_driveTest Drivevehicle, body
meetingMeetingbody, vehicle
write_upWriteUpamount, vehicle, body
quoteQuoteamount, vehicle, body
deliveryDeliveryvehicle, body
equity_alertNote*amount (equity), vehicle, body*no native equity type; renders as a clearly-prefixed Note

Example — an appraisal tool pushes a trade offer

curl -X POST https://dealercrm.com/partner/v1/activities \
  -H "Authorization: Bearer $DEALERCRM_KEY" -H "Content-Type: application/json" \
  -H "Idempotency-Key: appraise-5521" \
  -d '{
    "email": "jane@example.com",
    "kind": "appraisal",
    "amount": 18500,
    "reference": "ICO-5521",
    "vehicle": { "year": 2021, "make": "Toyota", "model": "RAV4", "vin": "2T3P1RFV5MW000000" },
    "body": "Instant cash offer, valid 7 days. Clean CARFAX, 2 keys.",
    "employee_name": "Appraisal Bot",
    "meta": { "book": "ICO", "condition": "clean", "expires": "2026-07-16" }
  }'

Example — a BDC tool sets an appointment

curl -X POST https://dealercrm.com/partner/v1/activities \
  -H "Authorization: Bearer $DEALERCRM_KEY" -H "Content-Type: application/json" \
  -d '{ "customer_id": 10000042, "kind": "appointment",
        "scheduled_at": "2026-07-12T15:00:00Z", "appointment_type": "meeting",
        "appointment_status": "confirmed", "employee_name": "BDC — Sam",
        "body": "Confirmed Saturday 11am to see the RAV4." }'

POST/inventory — inventory:write

A real-time status signal — mark a unit sold or available as it happens. Match the unit by inventory_id, vin, or stock. This is for status changes between feed syncs; bulk unit ingestion (new units, photos, full pricing) is handled by your inventory feed / DMS integration, not this endpoint.

# A real-time inventory system tells us a unit just sold:
curl -X POST https://dealercrm.com/partner/v1/inventory \
  -H "Authorization: Bearer $DEALERCRM_KEY" -H "Content-Type: application/json" \
  -d '{ "vin": "1FTFW1E86PKE00000", "status": "sold" }'
{ "object":"inventory", "inventory_id": 139450, "vin": "1FTFW1E86PKE00000",
  "stock": "A1234", "sale_status_type": 5, "status": "sold",
  "previous_sale_status_type": 1 }

Use "status": "available" when a deal falls through, or "sale_status_type": <code> for an exact code. A change fires the inventory.status_changed webhook.

Duplicate handling

You never have to worry about creating duplicate customers. Every lead/customer push runs through a dedup waterfall before anything is written; on a match we enrich the existing record instead of creating a new one.

The waterfall (first match wins)

#SignalMatch against
1Owned VINa vehicle the customer owns (owned_vin / trade) — never a sales-inquiry/inventory VIN
2Emailcase-insensitive
3Phonedigits-only, normalized to 10 digits
4Trade VINthe VIN being traded in

On a hit, we fill in blank fields, add any new phone/email we didn't have, attach owned/trade vehicles, and log conflicts — we never overwrite good data or split the customer across two records. The response tells you exactly what happened:

FieldMeaning
dedup_methodowned_vehicle_vin / email / phone / trade_vin when matched; new_customer when created
is_existing_customer (leads)true if we matched an existing person
created (customers)true only when a brand-new record was made

Exactly-once with Idempotency-Key

Dedup prevents different records for the same person. To make a retry of the same request safe (network blip, your queue re-runs), send an Idempotency-Key header (or a body external_id) that's stable for your source record. A repeat returns the original result — no second write.

-H "Idempotency-Key: yourcrm-lead-000123"   # same key on every retry of this lead

Best practices

• Send as many identifiers as you have (email + phone + name) so the waterfall can match confidently.
• Use a stable, unique Idempotency-Key per source record — your own primary key is ideal.
• Only pass owned_vin for a vehicle the customer actually owns/trades — never the inventory VIN they're asking about (many people inquire on the same stock #).
• Activities and inventory-status calls resolve an existing record (customer, or unit by VIN/stock) and update in place — they never create duplicates.

Webhooks (real-time push to you)

Register an HTTPS endpoint to receive events as they happen. Requires the webhooks:manage scope and webhooks enabled on the dealer's subscription.

POST/webhooks

curl -X POST https://dealercrm.com/partner/v1/webhooks \
  -H "Authorization: Bearer $DEALERCRM_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "url": "https://your-app.com/hooks/dealercrm",
        "events": ["lead.created","customer.created","activity.logged"] }'

The response includes a secretshown only once. Use ["*"] for all events. GET /webhooks lists them; DELETE /webhooks/{id} removes one.

Delivery

Each event is POSTed as JSON with these headers:

X-DealerCRM-Signature: t=1752083400,v1=<hex hmac-sha256>
X-DealerCRM-Event: lead.created
X-DealerCRM-Delivery: 8471

Body: { "id", "type", "created", "subdomain", "data" }. Failed deliveries retry with exponential backoff (up to ~24h); a chronically-failing endpoint is auto-disabled. Event types: lead.created, customer.created, customer.matched, activity.logged.

Verify the signature — required

Recompute the HMAC over <t>.<raw request body> with your signing secret, constant-time compare to v1, and reject if t is more than a few minutes old (replay protection).

// Node.js
const crypto = require('crypto');
function verify(rawBody, sigHeader, secret) {
  const p = Object.fromEntries(sigHeader.split(',').map(kv => kv.split('=')));
  const expected = crypto.createHmac('sha256', secret)
    .update(p.t + '.' + rawBody).digest('hex');
  const ok = expected.length === p.v1.length &&
    crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(p.v1));
  if (!ok) throw new Error('bad signature');
  if (Math.abs(Date.now()/1000 - Number(p.t)) > 300) throw new Error('stale');
}
# Python
import hmac, hashlib, time
def verify(raw_body: bytes, sig_header: str, secret: str):
    p = dict(kv.split('=', 1) for kv in sig_header.split(','))
    expected = hmac.new(secret.encode(),
        p['t'].encode() + b'.' + raw_body, hashlib.sha256).hexdigest()
    if not hmac.compare_digest(expected, p['v1']):
        raise ValueError('bad signature')
    if abs(time.time() - int(p['t'])) > 300:
        raise ValueError('stale')

Error codes

Errors are { "error": { "code", "message", … } }. Validation errors add a details array of { field, message }.

HTTPcodeMeaning
400invalid_json / bad_id / customer_id_requiredMalformed request
401missing_key / invalid_key / key_expiredBad, missing, or expired key
402subscription_expiredThe dealer's Partner API subscription lapsed
403subscription_inactive / insufficient_scope / webhooks_disabledNot enabled, or the key lacks the scope
404not_found / no_routeNo such record or endpoint
413payload_too_largeBody over 256 KB
422validation_failedA field failed validation (see details)
429rate_limitedOver the rate limit / daily quota (see Retry-After)

Data dictionary

Object shapes

Customer (from GET /customers)

{ "customer_id": 10000001,      // integer, stable id
  "first_name": "Jane", "last_name": "Doe", "full_name": "Jane Doe",
  "email": "jane@example.com",  // primary email
  "phone_mobile": "3105551234", "phone_home": null, "phone_work": null,
  "do_not_contact": false,
  "status_type": 1,
  "preferred_contact_channel": "sms",   // sms | email | call | null
  "point_person": { "id": 42, "name": "Alex Rivera" },  // assigned rep, or null
  "customer_since": "2025-03-01",
  "created": 1740787200 }         // unix seconds
// GET /customers/{id} adds: phones[], emails[], names[]

Inventory unit (from GET /inventory)

{ "inventory_id": 139450, "year": 2024, "make": "Ford", "model": "F-150",
  "trim": "XLT", "vin": "1FTFW1E86PKE00000", "stock_number": "A1234",
  "condition": "new", "mileage": 12,
  "ext_color": "Oxford White", "int_color": "Black",
  "msrp": 58230, "asking_price": 55990, "internet_price": 54990,
  "sale_status_type": 1,          // 1 = available, 5 = sold
  "primary_image_url": "https://…", "updated": "2026-07-09T16:15:49Z" }

Activity (from GET /activities)

{ "id": 48567841, "customer_id": 10000001, "activity_type": 2,
  "channel": "call", "direction": "1",   // "1" inbound, "2" outbound
  "created": 1752083400, "local_date": "2026-07-09",
  "body": "Connected • 3m 34s • from +13105551234\nRecording: https://…\n\n",
  "employee_name": "Alex Rivera", "lead_source": "partner_api",
  "vehicle": { "year": 2024, "make": "Ford", "model": "F-150", "vin": "…" },
  "is_olivia": false }

Enums & codes

FieldValues
lead intentsales · post_sales · parts · service · finance · other (default sales)
activity kindcomms: note · call_inbound · call_outbound · text_inbound · text_outbound · email_inbound · email_outbound · typed events: appraisal · trade_in · credit_app · service_ro · appointment · test_drive · meeting · write_up · quote · delivery · equity_alert
call outcomeconnected · voicemail · no_answer · missed · busy
email outcomesent · delivered · opened · clicked · bounced · unsubscribed
sale_status_type1 available (in stock) · 5 sold. Active inventory = anything except 5 and 6. Prefer status: "sold"|"available".
dedup_methodowned_vehicle_vin · email · phone · trade_vin · new_customer
webhook eventslead.created · customer.created · customer.matched · activity.logged · inventory.status_changed · *

Field formats

TypeFormat
VIN11–17 chars, A–Z + 0–9 excluding I/O/Q; uppercased automatically
Phoneany format accepted; normalized to 10 US digits (a leading 1 is dropped)
Emaillowercased; standard address format
Timestamps (in)send ISO 8601 for occurred_at / expires_at (e.g. 2026-07-09T18:30:00Z)
Timestamps (out)created / activity times are unix seconds; updated / webhook created are ISO 8601
IDscustomer_id, inventory_id are integers

Full code examples

Node.js — pull all active inventory

const KEY = process.env.DEALERCRM_KEY;
const BASE = 'https://dealercrm.com/partner/v1';

async function allInventory() {
  const out = [];
  let after = null;
  do {
    const url = new URL(BASE + '/inventory');
    url.searchParams.set('status', 'active');
    url.searchParams.set('limit', '100');
    if (after) url.searchParams.set('after', after);
    const res = await fetch(url, { headers: { Authorization: `Bearer ${KEY}` } });
    if (!res.ok) throw new Error('HTTP ' + res.status);
    const page = await res.json();
    out.push(...page.items);
    after = page.next_cursor;
  } while (after);
  return out;
}

Python — submit a lead

import os, requests
KEY = os.environ["DEALERCRM_KEY"]
r = requests.post("https://dealercrm.com/partner/v1/leads",
    headers={"Authorization": f"Bearer {KEY}",
             "Idempotency-Key": "acme-lead-9931"},
    json={"customer": {"full": "Jane Doe", "email": "jane@example.com",
                       "phone": "310-555-1234"},
          "vehicle": {"year": 2024, "make": "Ford", "model": "F-150"},
          "source": "Acme Marketing"})
r.raise_for_status()
print(r.json())

Cookbook — 100 real integrations

Concrete recipes for how different dealer tools use this API. Every one maps to the endpoints above. The workhorse is POST /activities (any "X happened with this customer" — calls, texts, emails, notes, with typed fields + a free-form meta); new opportunities go through POST /leads; identity through POST /customers; real-time inventory status through POST /inventory; reads through GET; and webhooks push events back to you. Bodies below show the key fields — add occurred_at, employee_name, meta, and Idempotency-Key freely.

Call tracking & telephony

#GoalCall + key fields
1Inbound sales call with recording + transcriptPOST /activities · kind=call_inbound, recording_url, transcript, duration_seconds, from, to, outcome=connected
2Outbound follow-up call, connectedPOST /activities · kind=call_outbound, outcome=connected, duration_seconds, employee_name
3Missed inbound callPOST /activities · kind=call_inbound, outcome=missed, duration_seconds=0
4Voicemail leftPOST /activities · kind=call_outbound, outcome=voicemail, recording_url
5First-time caller (unknown number) → new opportunityPOST /leads {customer.phone}, then log the call activity
6Call score / sentiment from call-analytics AIPOST /activities · kind=call_inbound, meta={call_score, sentiment}
7Click-to-call / text-to-connect outcomePOST /activities · kind=call_outbound, outcome, duration_seconds
8Service-line call (route to fixed-ops)POST /activities · kind=call_inbound, vehicle, meta={department:"service"}

Texting / SMS

#GoalCall + key fields
9Inbound text receivedPOST /activities · kind=text_inbound, body, from
10Outbound text sentPOST /activities · kind=text_outbound, body, status=sent
11Delivery receiptPOST /activities · kind=text_outbound, status=delivered, external_id
12New number texts in → leadPOST /leads {customer.phone, comments}
13SMS opt-in capturedPOST /activities · kind=note, meta={sms_optin:true, method}
14MMS with a photoPOST /activities · kind=text_inbound, body, meta={media:["https://…"]}

Email & marketing automation

#GoalCall + key fields
15Marketing email sentPOST /activities · kind=email_outbound, subject, campaign, outcome=sent
16Email openedPOST /activities · kind=email_outbound, subject, campaign, outcome=opened
17Email link clickedPOST /activities · kind=email_outbound, outcome=clicked, meta={url}
18Email bouncedPOST /activities · kind=email_outbound, outcome=bounced
19UnsubscribePOST /activities · kind=email_outbound, outcome=unsubscribed
20Inbound email replyPOST /activities · kind=email_inbound, subject, body, from
21Nurture-sequence touchPOST /activities · kind=email_outbound, campaign, meta={step}
22Newsletter signupPOST /activities · kind=note, campaign="Newsletter", meta={list}

Web chat & bots

#GoalCall + key fields
23Chat lead capturedPOST /leads {customer, comments, source:"Web Chat"}
24Chat transcript loggedPOST /activities · kind=note, body=transcript, meta={channel:"chat"}
25Bot → human handoffPOST /activities · kind=note, meta={handoff:true}
26After-hours chat requestPOST /leads {intent:"sales", comments}
27Chat appointment askPOST /leads {comments:"wants Sat 10am"}
28Chat satisfaction scorePOST /activities · kind=note, meta={csat}

Lead providers & marketplaces

#GoalCall + key fields
29New sales leadPOST /leads {customer, vehicle, source, intent:"sales"}
30Price-alert leadPOST /leads {vehicle, source, comments}
31Finance pre-qual leadPOST /leads {intent:"finance"}
32Trade-in leadPOST /leads {trade:{year,make,model,vin}}
33Service leadPOST /leads {intent:"service"}
34Same person, second lead → auto-dedupPOST /leads (same email/phone) → response is_existing_customer:true

Appraisal & trade-in

#GoalCall + key fields
35Trade appraisal received on a customer's carPOST /customers {owned_vin, full_name} → then POST /activities kind=appraisal, amount:18500, vehicle, meta={book:"ICO"} (→ Appraisal card)
36Instant cash offer issuedPOST /activities · kind=appraisal, amount:18500, vehicle, meta={ico:true, expires}
37Condition reportPOST /activities · kind=appraisal, vehicle, meta={condition, photos:[…]}
38Payoff / lien capturedPOST /activities · kind=trade_in, meta={payoff:14200, lienholder}
39Customer started an online appraisalPOST /leads {trade}
40Trade accepted into a dealPOST /activities · kind=trade_in, amount, vehicle, meta={accepted:true} (→ Trade-In card)
41Appraisal expiredPOST /activities · kind=note, meta={expired:true}

Inventory, merchandising & pricing

#GoalCall + key fields
42Unit just soldPOST /inventory {vin, status:"sold"}
43Deal fell through → unit back availablePOST /inventory {vin, status:"available"}
44Unit on hold / pendingPOST /inventory {vin, sale_status_type:<code>}
45Syndicate active inventory to a listing siteGET /inventory?status=active (paginate with after)
46Nightly sold reconcileGET /inventory?status=sold + POST /inventory for diffs
47Look up a unit by your VINGET /inventory then match VIN, or GET /inventory/{id}
48React to a sale in real timesubscribe webhook inventory.status_changed
49Get one unit's full detailGET /inventory/{id}
50Feed the whole lot to a BI toolGET /inventory?status=all (paginate)

BDC / appointment setters

#GoalCall + key fields
51Contact attempt, no answerPOST /activities · kind=call_outbound, outcome=no_answer
52Appointment setPOST /activities · kind=appointment, scheduled_at, appointment_type="meeting" (→ Appt Scheduled + counts)
53Appointment confirmedPOST /activities · kind=appointment, appointment_status="confirmed", scheduled_at
54Reminder sentPOST /activities · kind=text_outbound, campaign="Appt reminder"
55No-showPOST /activities · kind=appointment, appointment_status="no_show"
56RescheduledPOST /activities · kind=appointment, appointment_status="rescheduled", scheduled_at
57BDC → sales handoffPOST /activities · kind=note, employee_name

F&I / credit

#GoalCall + key fields
58Credit app submittedPOST /activities · kind=credit_app, status="Submitted", lender, amount (→ Credit App card)
59Prequal result / tierPOST /activities · kind=credit_app, status="Prequalified", meta={tier:"A"}
60F&I product sold (VSC/GAP)POST /activities · kind=note, meta={product:"VSC", amount:1899}
61Deal fundedPOST /activities · kind=note, meta={funded:true, amount}
62Contract signedPOST /activities · kind=note, meta={contract_id}
63Finance leadPOST /leads {intent:"finance"}

Reviews & reputation

#GoalCall + key fields
64Review request sentPOST /activities · kind=text_outbound or email_outbound, campaign="Review request"
65Review receivedPOST /activities · kind=note, meta={platform:"Google", rating:5, text}
66Low-rating alertPOST /activities · kind=note, meta={rating:1, platform}
67CSI survey sentPOST /activities · kind=email_outbound, campaign="CSI"
68Survey responsePOST /activities · kind=note, meta={csi_score}
69Reputation opt-outPOST /activities · kind=note, meta={opt_out:true}

Service & fixed-ops

#GoalCall + key fields
70Service appointment bookedPOST /activities · kind=appointment, appointment_type="service", scheduled_at, vehicle
71Repair order openedPOST /activities · kind=service_ro, ro_number:"12345", status="open", vehicle (→ Repair Order card)
72Repair order closedPOST /activities · kind=service_ro, ro_number:"12345", status="closed", amount:642, vehicle
73Declined service (upsell opportunity)POST /activities · kind=service_ro, meta={declined:["brakes"]}
74Open recall on an owned vehiclePOST /customers {owned_vin} → POST /activities kind=service_ro, meta={recall}
75Service visit completedPOST /activities · kind=service_ro, status="completed", vehicle
76Multi-point inspection resultsPOST /activities · kind=note, meta={mpi:{…}}

Digital retail & website

#GoalCall + key fields
77Online deal startedPOST /leads {intent:"sales", source:"Digital Retail", vehicle}
78Prequalified onlinePOST /activities · kind=note, meta={tier}
79Trade started onlinePOST /leads {trade}
80Deposit paidPOST /activities · kind=note, meta={deposit:500}
81Website form submissionPOST /leads {customer, comments}
82High-intent VDP viewPOST /activities · kind=note, vehicle, meta={event:"VDP view"}
83Deal / cart abandonedPOST /activities · kind=note, meta={abandoned:true}

Equity mining & in-market

#GoalCall + key fields
84Equity alertPOST /activities · kind=equity_alert, amount:3200, vehicle, meta={payoff}
85Lease-end opportunityPOST /activities · kind=note, meta={lease_end}
86Service-to-sales opportunityPOST /activities · kind=note, meta={signal:"high-mileage"}
87In-market scorePOST /activities · kind=note, meta={in_market_score:88}
88Upgrade / loyalty offer sentPOST /activities · kind=email_outbound, campaign="Upgrade"

Other AI tools

#GoalCall + key fields
89AI conversation summaryPOST /activities · kind=note, body=summary, meta={ai:true}
90AI-qualified leadPOST /leads {source:"AI Assistant", comments}
91AI-set appointmentPOST /activities · kind=note, meta={appt_at, ai:true}
92AI sentiment / intent tagPOST /activities · kind=note, meta={sentiment, intent}
93AI voice agent handled a callPOST /activities · kind=call_inbound, transcript, recording_url, meta={ai:true}

Analytics & data sync

#GoalCall + key fields
94Nightly customer exportGET /customers (paginate with after)
95One customer's full historyGET /activities?customer_id=…
96Inventory snapshot for BIGET /inventory?status=all (paginate)
97Verify a key / health checkGET /ping
98Real-time event streamPOST /webhooks {events:["*"]}

Video & engagement

#GoalCall + key fields
99Personalized video sentPOST /activities · kind=email_outbound or note, campaign="Video", meta={video_url}
100Video watchedPOST /activities · kind=note, meta={video_url, watched_seconds:47}