Test Drive ProTest Drive ProDevelopers
Test Drive Pro · Partner API

Connect the drive.
Complete the picture.

Bring dealership inventory, test drives and recorded sales outcomes together through a secure, scoped integration.

One dealership per key

Explicit permissions and server-side tenant isolation.

Traceable outcomes

Stable event IDs and receipts for safe retries.

Clear data boundaries

Only the fields your integration is authorized to use.

Base URL

https://www.testdrivepro.co/api/partner/v1
Production API access requires a provisioned partner key. Documentation access does not activate an integration or grant access to dealership data.

Your first request

  1. Agree on the dealership mapping and required scopes with TestDrivePro.
  2. Receive your key through an approved private channel and store it on your server.
  3. Request inventory, then follow next_cursor until it is null.
curl --fail-with-body "https://www.testdrivepro.co/api/partner/v1/inventory?limit=50" \
  --header "Authorization: Bearer $TDP_API_KEY" \
  --header "Accept: application/json"

Use the downloadable OpenAPI 3.1 specification for exact request and response schemas. Examples contain placeholders; never use real license information as a development fixture.

Authentication and access

Send Authorization: Bearer <partner key> with every request. Each key belongs to one partner connection and one dealership. The server derives the tenant from the key; callers cannot select another dealership.

Scopes are explicitly granted. Keys can expire, be revoked or be rotated. Keep credentials in a server secret manager, out of browser code, URLs and logs. All data responses use Cache-Control: no-store.

Limit: 120 authorized requests per minute per key. A 429 response includes Retry-After: 60.

API reference

Expand an endpoint for its parameters and contract. The schemas below come from the same versioned OpenAPI file you download.

GET/inventoryList inventory

Requires inventory:read. Tenant comes only from the partner key. No implicit available-only inventory filter. Start with no cursor; enumerate again for a fresh reconciliation.

ParameterLocationContract
cursorquery{"type":"integer","minimum":1,"maximum":2147483647}
limitquery{"type":"integer","minimum":1,"maximum":100,"default":50}

Responses

{
  "200": {
    "description": "Tenant-scoped page",
    "content": {
      "application/json": {
        "schema": {
          "$ref": "#/components/schemas/InventoryPage"
        }
      }
    }
  },
  "400": {
    "$ref": "#/components/responses/Error"
  },
  "401": {
    "$ref": "#/components/responses/Error"
  },
  "403": {
    "$ref": "#/components/responses/Error"
  },
  "429": {
    "$ref": "#/components/responses/Error"
  },
  "503": {
    "$ref": "#/components/responses/Error"
  }
}
GET/drivesList drives

Requires drives:read. Tenant comes only from the partner key. No implicit available-only inventory filter. Start with no cursor; enumerate again for a fresh reconciliation.

ParameterLocationContract
cursorquery{"type":"integer","minimum":1,"maximum":2147483647}
limitquery{"type":"integer","minimum":1,"maximum":100,"default":50}

Responses

{
  "200": {
    "description": "Tenant-scoped page",
    "content": {
      "application/json": {
        "schema": {
          "$ref": "#/components/schemas/DrivePage"
        }
      }
    }
  },
  "400": {
    "$ref": "#/components/responses/Error"
  },
  "401": {
    "$ref": "#/components/responses/Error"
  },
  "403": {
    "$ref": "#/components/responses/Error"
  },
  "429": {
    "$ref": "#/components/responses/Error"
  },
  "503": {
    "$ref": "#/components/responses/Error"
  }
}
GET/customersList customers

Requires customers:read. Tenant comes only from the partner key. No implicit available-only inventory filter. Start with no cursor; enumerate again for a fresh reconciliation.

ParameterLocationContract
cursorquery{"type":"integer","minimum":1,"maximum":2147483647}
limitquery{"type":"integer","minimum":1,"maximum":100,"default":50}

Responses

{
  "200": {
    "description": "Tenant-scoped page",
    "content": {
      "application/json": {
        "schema": {
          "$ref": "#/components/schemas/CustomerPage"
        }
      }
    }
  },
  "400": {
    "$ref": "#/components/responses/Error"
  },
  "401": {
    "$ref": "#/components/responses/Error"
  },
  "403": {
    "$ref": "#/components/responses/Error"
  },
  "429": {
    "$ref": "#/components/responses/Error"
  },
  "503": {
    "$ref": "#/components/responses/Error"
  }
}
GET/notesList notes

Requires notes:read. Tenant comes only from the partner key. No implicit available-only inventory filter. Start with no cursor; enumerate again for a fresh reconciliation.

ParameterLocationContract
cursorquery{"type":"integer","minimum":1,"maximum":2147483647}
limitquery{"type":"integer","minimum":1,"maximum":100,"default":50}

Responses

{
  "200": {
    "description": "Tenant-scoped page",
    "content": {
      "application/json": {
        "schema": {
          "$ref": "#/components/schemas/DriveNotePage"
        }
      }
    }
  },
  "400": {
    "$ref": "#/components/responses/Error"
  },
  "401": {
    "$ref": "#/components/responses/Error"
  },
  "403": {
    "$ref": "#/components/responses/Error"
  },
  "429": {
    "$ref": "#/components/responses/Error"
  },
  "503": {
    "$ref": "#/components/responses/Error"
  }
}
POST/outcomesQueue an exact-drive outcome report

Requires outcomes:write and current dealer mutation access. 202 is an immutable queued receipt only: no drive, inventory or commission is changed. Same event and normalized body replays the receipt; conflicting content returns409. Body limit8KiB. Unknown fields rejected. Operational purposes return 409 unsupported_drive_purpose before queuing.

Request body

{
  "required": true,
  "content": {
    "application/json": {
      "schema": {
        "$ref": "#/components/schemas/OutcomeInput"
      }
    }
  }
}

Responses

{
  "202": {
    "description": "Queued or replayed receipt; not an applied sale",
    "content": {
      "application/json": {
        "schema": {
          "$ref": "#/components/schemas/OutcomeReceipt"
        }
      }
    }
  },
  "400": {
    "$ref": "#/components/responses/Error"
  },
  "401": {
    "$ref": "#/components/responses/Error"
  },
  "403": {
    "$ref": "#/components/responses/Error"
  },
  "404": {
    "$ref": "#/components/responses/Error"
  },
  "409": {
    "$ref": "#/components/responses/Error"
  },
  "413": {
    "$ref": "#/components/responses/Error"
  },
  "429": {
    "$ref": "#/components/responses/Error"
  },
  "503": {
    "$ref": "#/components/responses/Error"
  }
}
GET/outcomes/{external_event_id}Read a queued outcome receipt

Requires outcomes:read. The lookup is scoped to the key connection and tenant.

ParameterLocationContract
external_event_id *path{"type":"string","pattern":"^[A-Za-z0-9_.:-]{1,160}$"}

Responses

{
  "200": {
    "description": "Receipt status",
    "content": {
      "application/json": {
        "schema": {
          "$ref": "#/components/schemas/OutcomeReceipt"
        }
      }
    }
  },
  "400": {
    "$ref": "#/components/responses/Error"
  },
  "401": {
    "$ref": "#/components/responses/Error"
  },
  "403": {
    "$ref": "#/components/responses/Error"
  },
  "404": {
    "$ref": "#/components/responses/Error"
  },
  "429": {
    "$ref": "#/components/responses/Error"
  },
  "503": {
    "$ref": "#/components/responses/Error"
  }
}
GET/staffList staff

Requires staff:read and activated extended reads. Current tenant salesperson records and their stored user references; no email, pay or commission data.

ParameterLocationContract
cursorquery{"type":"integer","minimum":1,"maximum":2147483647}
limitquery{"type":"integer","minimum":1,"maximum":100,"default":50}

Responses

{
  "200": {
    "description": "Tenant-scoped page",
    "content": {
      "application/json": {
        "schema": {
          "$ref": "#/components/schemas/StaffPage"
        }
      }
    }
  },
  "400": {
    "$ref": "#/components/responses/Error"
  },
  "401": {
    "$ref": "#/components/responses/Error"
  },
  "403": {
    "$ref": "#/components/responses/Error"
  },
  "429": {
    "$ref": "#/components/responses/Error"
  },
  "503": {
    "$ref": "#/components/responses/Error"
  }
}
GET/liveList live

Requires live:read and activated extended reads. Active drives and recorded open tracking sessions only. A session_open state does not prove the phone is online. No GPS, speed, device ID or route data.

ParameterLocationContract
cursorquery{"type":"integer","minimum":1,"maximum":2147483647}
limitquery{"type":"integer","minimum":1,"maximum":100,"default":50}

Responses

{
  "200": {
    "description": "Tenant-scoped page",
    "content": {
      "application/json": {
        "schema": {
          "$ref": "#/components/schemas/LivePage"
        }
      }
    }
  },
  "400": {
    "$ref": "#/components/responses/Error"
  },
  "401": {
    "$ref": "#/components/responses/Error"
  },
  "403": {
    "$ref": "#/components/responses/Error"
  },
  "429": {
    "$ref": "#/components/responses/Error"
  },
  "503": {
    "$ref": "#/components/responses/Error"
  }
}
GET/agreementsList agreements

Requires agreements:read and activated extended reads. Agreement metadata references only. Does not grant content, signature or PDF access; no URLs.

ParameterLocationContract
cursorquery{"type":"integer","minimum":1,"maximum":2147483647}
limitquery{"type":"integer","minimum":1,"maximum":100,"default":50}

Responses

{
  "200": {
    "description": "Tenant-scoped page",
    "content": {
      "application/json": {
        "schema": {
          "$ref": "#/components/schemas/AgreementReferencePage"
        }
      }
    }
  },
  "400": {
    "$ref": "#/components/responses/Error"
  },
  "401": {
    "$ref": "#/components/responses/Error"
  },
  "403": {
    "$ref": "#/components/responses/Error"
  },
  "429": {
    "$ref": "#/components/responses/Error"
  },
  "503": {
    "$ref": "#/components/responses/Error"
  }
}
GET/documentsList documents

Requires documents:read and activated extended reads. Exact drive/customer-associated license capture metadata only. No URLs, image bytes, license fields or verification status.

ParameterLocationContract
cursorquery{"type":"string","format":"uuid"}
limitquery{"type":"integer","minimum":1,"maximum":100,"default":50}

Responses

{
  "200": {
    "description": "Tenant-scoped page",
    "content": {
      "application/json": {
        "schema": {
          "$ref": "#/components/schemas/DocumentReferencePage"
        }
      }
    }
  },
  "400": {
    "$ref": "#/components/responses/Error"
  },
  "401": {
    "$ref": "#/components/responses/Error"
  },
  "403": {
    "$ref": "#/components/responses/Error"
  },
  "429": {
    "$ref": "#/components/responses/Error"
  },
  "503": {
    "$ref": "#/components/responses/Error"
  }
}
GET/note-eventsList note-events

Requires notes:read and activated extended reads. Allowlisted existing audit note/issue changes, recorded actor and time. Current vehicle is labeled current, not historical. Null actor is not inferred. Source audit may be incomplete; no outbound delivery or partner acknowledgment. Continue through empty filtered pages while cursor is non-null.

ParameterLocationContract
cursorqueryLast event ID as an exact integer up to 9007199254740991. Continue until next_cursor is null, including filtered empty pages.{"type":"string","pattern":"^[1-9][0-9]{0,15}$"}
limitquery{"type":"integer","minimum":1,"maximum":100,"default":50}

Responses

{
  "200": {
    "description": "Tenant-scoped page",
    "content": {
      "application/json": {
        "schema": {
          "$ref": "#/components/schemas/NoteEventPage"
        }
      }
    }
  },
  "400": {
    "$ref": "#/components/responses/Error"
  },
  "401": {
    "$ref": "#/components/responses/Error"
  },
  "403": {
    "$ref": "#/components/responses/Error"
  },
  "429": {
    "$ref": "#/components/responses/Error"
  },
  "503": {
    "$ref": "#/components/responses/Error"
  }
}
All response and request models
Inventory
{
  "type": "object",
  "additionalProperties": false,
  "required": [
    "id",
    "year",
    "make",
    "model",
    "trim",
    "price",
    "miles",
    "vin",
    "stock",
    "color",
    "status",
    "days_on_lot",
    "created_at",
    "updated_at"
  ],
  "properties": {
    "id": {
      "type": "integer",
      "minimum": 1
    },
    "year": {
      "type": [
        "integer",
        "null"
      ]
    },
    "make": {
      "type": [
        "string",
        "null"
      ]
    },
    "model": {
      "type": [
        "string",
        "null"
      ]
    },
    "trim": {
      "type": [
        "string",
        "null"
      ]
    },
    "price": {
      "type": [
        "number",
        "null"
      ]
    },
    "miles": {
      "type": [
        "integer",
        "null"
      ]
    },
    "vin": {
      "type": [
        "string",
        "null"
      ]
    },
    "stock": {
      "type": [
        "string",
        "null"
      ]
    },
    "color": {
      "type": [
        "string",
        "null"
      ]
    },
    "status": {
      "type": [
        "string",
        "null"
      ]
    },
    "days_on_lot": {
      "type": [
        "integer",
        "null"
      ]
    },
    "created_at": {
      "type": [
        "string",
        "null"
      ]
    },
    "updated_at": {
      "type": [
        "string",
        "null"
      ]
    }
  }
}
InventoryPage
{
  "type": "object",
  "additionalProperties": false,
  "required": [
    "data",
    "next_cursor"
  ],
  "properties": {
    "data": {
      "type": "array",
      "items": {
        "$ref": "#/components/schemas/Inventory"
      }
    },
    "next_cursor": {
      "type": [
        "string",
        "null"
      ],
      "pattern": "^[1-9][0-9]*$",
      "description": "Continue until next_cursor is null; a filtered page may have no items and still have a cursor."
    }
  }
}
Drive
{
  "type": "object",
  "additionalProperties": false,
  "required": [
    "id",
    "customer_id",
    "vehicle_id",
    "salesperson_id",
    "date",
    "status",
    "outcome",
    "created_at",
    "updated_at",
    "drive_purpose"
  ],
  "properties": {
    "id": {
      "type": "integer",
      "minimum": 1
    },
    "customer_id": {
      "type": [
        "integer",
        "null"
      ]
    },
    "vehicle_id": {
      "type": [
        "integer",
        "null"
      ]
    },
    "salesperson_id": {
      "type": [
        "integer",
        "null"
      ]
    },
    "date": {
      "type": [
        "string",
        "null"
      ]
    },
    "status": {
      "type": [
        "string",
        "null"
      ]
    },
    "outcome": {
      "type": [
        "string",
        "null"
      ],
      "enum": [
        "sold",
        "not_sold",
        null
      ]
    },
    "created_at": {
      "type": [
        "string",
        "null"
      ]
    },
    "updated_at": {
      "type": [
        "string",
        "null"
      ]
    },
    "drive_purpose": {
      "type": "string",
      "enum": [
        "test_drive",
        "ppi",
        "employee_use",
        "borrowed_vehicle"
      ],
      "description": "Operational uses are not sales test drives and do not accept outcomes. Legacy records are test_drive."
    },
    "started_at": {
      "type": [
        "string",
        "null"
      ],
      "format": "date-time"
    },
    "started_at_source": {
      "type": "string",
      "enum": [
        "start_timestamp",
        "not_recorded"
      ]
    },
    "returned_at": {
      "type": [
        "string",
        "null"
      ],
      "format": "date-time"
    }
  }
}
DrivePage
{
  "type": "object",
  "additionalProperties": false,
  "required": [
    "data",
    "next_cursor"
  ],
  "properties": {
    "data": {
      "type": "array",
      "items": {
        "$ref": "#/components/schemas/Drive"
      }
    },
    "next_cursor": {
      "type": [
        "string",
        "null"
      ],
      "pattern": "^[1-9][0-9]*$",
      "description": "Continue until next_cursor is null; a filtered page may have no items and still have a cursor."
    }
  }
}
Customer
{
  "type": "object",
  "additionalProperties": false,
  "required": [
    "id",
    "first_name",
    "last_name",
    "phone",
    "email",
    "created_at"
  ],
  "properties": {
    "id": {
      "type": "integer",
      "minimum": 1
    },
    "first_name": {
      "type": [
        "string",
        "null"
      ]
    },
    "last_name": {
      "type": [
        "string",
        "null"
      ]
    },
    "phone": {
      "type": [
        "string",
        "null"
      ]
    },
    "email": {
      "type": [
        "string",
        "null"
      ]
    },
    "created_at": {
      "type": [
        "string",
        "null"
      ]
    },
    "contact_consent": {
      "type": [
        "boolean",
        "null"
      ]
    },
    "contact_consent_record": {
      "type": "string",
      "enum": [
        "stored_boolean",
        "not_recorded"
      ],
      "description": "A stored false may be a historical default. No timestamp, channel permission or legal consent is inferred."
    }
  }
}
CustomerPage
{
  "type": "object",
  "additionalProperties": false,
  "required": [
    "data",
    "next_cursor"
  ],
  "properties": {
    "data": {
      "type": "array",
      "items": {
        "$ref": "#/components/schemas/Customer"
      }
    },
    "next_cursor": {
      "type": [
        "string",
        "null"
      ],
      "pattern": "^[1-9][0-9]*$",
      "description": "Continue until next_cursor is null; a filtered page may have no items and still have a cursor."
    }
  }
}
DriveNote
{
  "type": "object",
  "additionalProperties": false,
  "required": [
    "id",
    "vehicle_id",
    "notes",
    "issues",
    "updated_at"
  ],
  "properties": {
    "id": {
      "type": "integer",
      "minimum": 1
    },
    "vehicle_id": {
      "type": [
        "integer",
        "null"
      ]
    },
    "notes": {
      "type": [
        "string",
        "null"
      ]
    },
    "issues": {
      "type": [
        "string",
        "null"
      ]
    },
    "updated_at": {
      "type": [
        "string",
        "null"
      ]
    }
  }
}
DriveNotePage
{
  "type": "object",
  "additionalProperties": false,
  "required": [
    "data",
    "next_cursor"
  ],
  "properties": {
    "data": {
      "type": "array",
      "items": {
        "$ref": "#/components/schemas/DriveNote"
      }
    },
    "next_cursor": {
      "type": [
        "string",
        "null"
      ],
      "pattern": "^[1-9][0-9]*$",
      "description": "Continue until next_cursor is null; a filtered page may have no items and still have a cursor."
    }
  }
}
OutcomeInput
{
  "type": "object",
  "additionalProperties": false,
  "required": [
    "external_dealer_id",
    "external_event_id",
    "external_deal_id",
    "tdp_drive_id",
    "outcome",
    "reported_at"
  ],
  "properties": {
    "external_dealer_id": {
      "type": "string",
      "minLength": 1,
      "maxLength": 120,
      "description": "Must equal the external dealer configured on this key connection."
    },
    "external_event_id": {
      "type": "string",
      "pattern": "^[A-Za-z0-9_.:-]{1,160}$",
      "description": "Stable idempotency identity within the connection; survives key rotation."
    },
    "external_deal_id": {
      "type": "string",
      "minLength": 1,
      "maxLength": 160
    },
    "tdp_drive_id": {
      "type": "integer",
      "minimum": 1,
      "maximum": 2147483647,
      "description": "Exact ID obtained from this tenant; never guessed by customer name."
    },
    "outcome": {
      "type": [
        "string",
        "null"
      ],
      "enum": [
        "sold",
        "not_sold",
        null
      ],
      "description": "Explicit null reports a request to clear an outcome. None of these values is applied in v1."
    },
    "reported_at": {
      "type": "string",
      "format": "date-time",
      "description": "Calendar-valid timestamp with seconds and explicit timezone, normalized to UTC."
    }
  },
  "example": {
    "external_dealer_id": "sandbox-dealer-1",
    "external_event_id": "sale-event-001",
    "external_deal_id": "sandbox-deal-001",
    "tdp_drive_id": 101,
    "outcome": "sold",
    "reported_at": "2026-10-02T12:00:00Z"
  }
}
OutcomeReceipt
{
  "type": "object",
  "additionalProperties": false,
  "required": [
    "id",
    "external_event_id",
    "tdp_drive_id",
    "status",
    "applied",
    "created_at"
  ],
  "properties": {
    "id": {
      "type": "string",
      "format": "uuid"
    },
    "external_event_id": {
      "type": "string"
    },
    "tdp_drive_id": {
      "type": "integer"
    },
    "status": {
      "type": "string",
      "enum": [
        "queued",
        "applied",
        "rejected"
      ]
    },
    "applied": {
      "type": "boolean",
      "description": "False for every newly queued event. No applier is part of this version."
    },
    "duplicate": {
      "type": "boolean",
      "description": "Included by POST: same normalized payload replay returned the original receipt."
    },
    "created_at": {
      "type": "string",
      "format": "date-time"
    },
    "reviewed_at": {
      "type": [
        "string",
        "null"
      ],
      "format": "date-time"
    }
  }
}
Error
{
  "type": "object",
  "additionalProperties": false,
  "required": [
    "error"
  ],
  "properties": {
    "error": {
      "type": "object",
      "additionalProperties": false,
      "required": [
        "code",
        "message"
      ],
      "properties": {
        "code": {
          "type": "string"
        },
        "message": {
          "type": "string"
        }
      }
    }
  }
}
Staff
{
  "type": "object",
  "additionalProperties": false,
  "required": [
    "id",
    "user_id",
    "name",
    "active"
  ],
  "properties": {
    "id": {
      "type": "integer",
      "minimum": 1
    },
    "user_id": {
      "type": [
        "string",
        "null"
      ]
    },
    "name": {
      "type": [
        "string",
        "null"
      ]
    },
    "active": {
      "type": "boolean"
    }
  }
}
Live
{
  "type": "object",
  "additionalProperties": false,
  "required": [
    "drive_id",
    "vehicle_id",
    "salesperson_id",
    "status",
    "drive_purpose",
    "started_at",
    "tracking_state",
    "session_consented_at",
    "last_sample_recorded_at"
  ],
  "properties": {
    "drive_id": {
      "type": "integer",
      "minimum": 1
    },
    "vehicle_id": {
      "type": [
        "integer",
        "null"
      ]
    },
    "salesperson_id": {
      "type": [
        "integer",
        "null"
      ]
    },
    "status": {
      "const": "active"
    },
    "drive_purpose": {
      "type": "string",
      "enum": [
        "test_drive",
        "ppi",
        "employee_use",
        "borrowed_vehicle"
      ],
      "description": "Operational uses are not sales test drives and do not accept outcomes. Legacy records are test_drive."
    },
    "started_at": {
      "type": [
        "string",
        "null"
      ],
      "format": "date-time"
    },
    "tracking_state": {
      "enum": [
        "session_open",
        "no_open_session"
      ],
      "description": "Recorded session state only; session_open does not establish that the phone is online."
    },
    "session_consented_at": {
      "type": [
        "string",
        "null"
      ],
      "format": "date-time"
    },
    "last_sample_recorded_at": {
      "type": [
        "string",
        "null"
      ],
      "format": "date-time"
    }
  }
}
AgreementReference
{
  "type": "object",
  "additionalProperties": false,
  "required": [
    "id",
    "test_drive_id",
    "customer_id",
    "vehicle_id",
    "salesperson_id",
    "signed_at",
    "reference_type",
    "content_access"
  ],
  "properties": {
    "id": {
      "type": "integer",
      "minimum": 1
    },
    "test_drive_id": {
      "type": "integer",
      "minimum": 1
    },
    "customer_id": {
      "type": "integer",
      "minimum": 1
    },
    "vehicle_id": {
      "type": "integer",
      "minimum": 1
    },
    "salesperson_id": {
      "type": "integer",
      "minimum": 1
    },
    "signed_at": {
      "type": [
        "string",
        "null"
      ],
      "format": "date-time"
    },
    "reference_type": {
      "const": "agreement_metadata"
    },
    "content_access": {
      "const": "not_granted"
    }
  }
}
DocumentReference
{
  "type": "object",
  "additionalProperties": false,
  "required": [
    "id",
    "test_drive_id",
    "customer_id",
    "recorded_at",
    "available_sides",
    "reference_type",
    "content_access"
  ],
  "properties": {
    "id": {
      "type": "string",
      "format": "uuid"
    },
    "test_drive_id": {
      "type": "integer",
      "minimum": 1
    },
    "customer_id": {
      "type": "integer",
      "minimum": 1
    },
    "recorded_at": {
      "type": [
        "string",
        "null"
      ],
      "format": "date-time"
    },
    "available_sides": {
      "type": "array",
      "items": {
        "enum": [
          "front",
          "back"
        ]
      },
      "uniqueItems": true,
      "minItems": 1,
      "description": "Sides with a recorded tenant-owned path reference; not a storage existence or content-access guarantee."
    },
    "reference_type": {
      "const": "license_capture_metadata"
    },
    "content_access": {
      "const": "not_granted"
    }
  }
}
NoteEvent
{
  "type": "object",
  "additionalProperties": false,
  "required": [
    "event_id",
    "test_drive_id",
    "current_vehicle_id",
    "recorded_actor_id",
    "recorded_actor_role",
    "recorded_at",
    "changes",
    "recorded_actor_name"
  ],
  "properties": {
    "event_id": {
      "type": "integer",
      "minimum": 1,
      "maximum": 9007199254740991
    },
    "test_drive_id": {
      "type": "integer",
      "minimum": 1
    },
    "current_vehicle_id": {
      "type": [
        "integer",
        "null"
      ]
    },
    "recorded_actor_id": {
      "type": [
        "string",
        "null"
      ]
    },
    "recorded_actor_role": {
      "type": [
        "string",
        "null"
      ]
    },
    "recorded_at": {
      "type": [
        "string",
        "null"
      ],
      "format": "date-time"
    },
    "changes": {
      "type": "object",
      "additionalProperties": false,
      "minProperties": 1,
      "properties": {
        "notes": {
          "type": [
            "string",
            "null"
          ]
        },
        "issues": {
          "type": [
            "string",
            "null"
          ]
        }
      }
    },
    "recorded_actor_name": {
      "type": [
        "string",
        "null"
      ],
      "description": "Actual actor display name recorded with the audit event; not inferred from the assigned salesperson."
    }
  }
}
PullReceipt
{
  "type": "object",
  "additionalProperties": false,
  "required": [
    "kind",
    "retrieved_at",
    "source_cursor",
    "next_cursor",
    "item_count",
    "page_sha256",
    "delivery_status"
  ],
  "properties": {
    "kind": {
      "const": "read_only_pull"
    },
    "retrieved_at": {
      "type": "string",
      "format": "date-time"
    },
    "source_cursor": {
      "type": [
        "string",
        "null"
      ]
    },
    "next_cursor": {
      "type": [
        "string",
        "null"
      ],
      "description": "Continue until next_cursor is null; a filtered page may have no items and still have a cursor."
    },
    "item_count": {
      "type": "integer",
      "minimum": 0
    },
    "page_sha256": {
      "type": "string",
      "pattern": "^[a-f0-9]{64}$"
    },
    "delivery_status": {
      "const": "not_sent"
    }
  }
}
StaffPage
{
  "type": "object",
  "additionalProperties": false,
  "required": [
    "data",
    "next_cursor"
  ],
  "properties": {
    "data": {
      "type": "array",
      "items": {
        "$ref": "#/components/schemas/Staff"
      }
    },
    "next_cursor": {
      "type": [
        "string",
        "null"
      ],
      "description": "Continue until next_cursor is null; a filtered page may have no items and still have a cursor.",
      "pattern": "^[1-9][0-9]*$"
    }
  }
}
LivePage
{
  "type": "object",
  "additionalProperties": false,
  "required": [
    "data",
    "next_cursor"
  ],
  "properties": {
    "data": {
      "type": "array",
      "items": {
        "$ref": "#/components/schemas/Live"
      }
    },
    "next_cursor": {
      "type": [
        "string",
        "null"
      ],
      "description": "Continue until next_cursor is null; a filtered page may have no items and still have a cursor.",
      "pattern": "^[1-9][0-9]*$"
    }
  }
}
AgreementReferencePage
{
  "type": "object",
  "additionalProperties": false,
  "required": [
    "data",
    "next_cursor"
  ],
  "properties": {
    "data": {
      "type": "array",
      "items": {
        "$ref": "#/components/schemas/AgreementReference"
      }
    },
    "next_cursor": {
      "type": [
        "string",
        "null"
      ],
      "description": "Continue until next_cursor is null; a filtered page may have no items and still have a cursor.",
      "pattern": "^[1-9][0-9]*$"
    }
  }
}
DocumentReferencePage
{
  "type": "object",
  "additionalProperties": false,
  "required": [
    "data",
    "next_cursor"
  ],
  "properties": {
    "data": {
      "type": "array",
      "items": {
        "$ref": "#/components/schemas/DocumentReference"
      }
    },
    "next_cursor": {
      "type": [
        "string",
        "null"
      ],
      "description": "Continue until next_cursor is null; a filtered page may have no items and still have a cursor.",
      "format": "uuid"
    }
  }
}
NoteEventPage
{
  "type": "object",
  "additionalProperties": false,
  "required": [
    "data",
    "next_cursor",
    "receipt"
  ],
  "properties": {
    "data": {
      "type": "array",
      "items": {
        "$ref": "#/components/schemas/NoteEvent"
      }
    },
    "next_cursor": {
      "type": [
        "string",
        "null"
      ],
      "description": "Continue until next_cursor is null; a filtered page may have no items and still have a cursor.",
      "pattern": "^[1-9][0-9]*$"
    },
    "receipt": {
      "$ref": "#/components/schemas/PullReceipt"
    }
  }
}

Read every page

List endpoints accept limit from 1 to 100 (default 50) and an optional cursor. Responses contain data and next_cursor. Pass the returned cursor unchanged. Stop only when it is null—even a short or empty filtered page may have a next cursor.

Most cursors are numeric IDs; document cursors are capture UUIDs. Enumeration is not a frozen snapshot or an incremental change feed. Restart without a cursor for a new reconciliation. Never infer a sale from a missing inventory record.

Report an outcome safely

Submit an exact TestDrivePro drive ID with your stable external event ID. A 202 response means the report is queued for owner review. It does not mean a sale, inventory change or commission has been applied.

{
  "external_dealer_id": "YOUR_DEALER_ID",
  "external_event_id": "YOUR_UNIQUE_EVENT_ID",
  "external_deal_id": "YOUR_DEAL_ID",
  "tdp_drive_id": 101,
  "outcome": "sold",
  "reported_at": "2026-10-03T12:00:00Z"
}

The drive ID above is illustrative; replace it with an actual authorized drive ID. Supported outcomes are sold, not_sold and an explicit null to request clearing an outcome. Omitted or extra fields are rejected. Maximum body size is 8 KiB.

Retry the same event ID with the same normalized body to retrieve the same receipt, including after key rotation. Reusing it with changed content returns 409 idempotency_conflict. Operational vehicle uses such as PPI do not accept sales outcomes.

Errors and retries

StatusWhat to do
400 / 413Correct the request fields or size before retrying.
401 / 403Check key validity, expiry and assigned scopes.
404Check the resource ID and the key’s dealership connection.
409Resolve an event conflict or unsupported drive purpose; do not invent a new event ID to bypass it.
429Wait at least the Retry-After interval, then retry.
503 / network failureUse bounded exponential backoff with jitter. Preserve the outcome event ID and body.

Error responses follow { "error": { "code": "…", "message": "…" } }. Inspect the code rather than parsing the display message.

Know what the API shares

Inventory and drive scopes expose operational identifiers and fields. Customer contact information requires customers:read. Staff notes require notes:read and may contain sensitive free text.

Agreement and document endpoints return references and metadata. They do not grant access to files. Live state describes recorded tracking-session activity; it does not guarantee a phone is currently transmitting.

This API does not export ID photos, license numbers, dates of birth, signature bytes, agreement contents, GPS coordinates, commissions or provider credentials.

Recorded contact consent is a stored value, not proof of authorization for a new purpose. Agree on data use and retention before enabling any personal-data scope.

Ready for technical scoping

For Wayne Reaves or another DMS partner, confirm these details together before connecting production:

  • Sandbox access, authentication and external dealership, vehicle, customer and deal identifiers.
  • Required data fields, scopes and reconciliation frequency.
  • Sale reversals, corrected deals and idempotency rules.
  • Inventory/recon note acknowledgments and accepted sales-intake fields.
  • Ownership of signup links, the seven-day trial entry point and any SSO.

The current API supports polling. Webhooks, partner-hosted popups, SSO and automatic partner delivery are not part of this contract.