23 KiB
Vehicle Data Platform API Contract
Response Envelope
All platform APIs return the same envelope:
{
"data": {},
"traceId": "trace-20260704000000.000000",
"timestamp": 1783094400000
}
Pagination uses:
{
"items": [],
"total": 0,
"limit": 20,
"offset": 0
}
Vehicle Keyword
The platform is vehicle-first. Query APIs should accept keyword as the user-facing vehicle selector.
keyword may be:
- VIN
- plate
- JT808 phone
The BFF resolves keyword through vehicle identity data before querying realtime, history, RAW, or mileage data. vin remains accepted as a compatibility alias on data query APIs, but new UI and integrations should send keyword.
If a keyword cannot be resolved to a VIN, data APIs must not fabricate a VIN. They should return empty result pages for vehicle data, while /api/vehicle-service exposes lookupResolved=false and quality issues for follow-up binding work.
Core Query APIs
Unified Metric Catalog
GET /api/v2/metrics
Returns the server-owned metric whitelist used by rule configuration and future telemetry/history surfaces. Production definitions come from vehicle_metric_definition plus vehicle_metric_protocol_mapping; migration 005_metric_catalog.sql only seeds missing rows, so later database configuration is not overwritten by releases. Each metric includes key, localized label, unit, category, valueType, supported protocols, per-protocol sourceFields, and searchable/chartable/alertable capabilities. Alert rule writes reject unknown, disabled, non-alertable, type-mismatched, or evaluator-unsupported metrics; clients must not invent metric keys or derive SQL fields from user input. /api/v2/history/metrics remains the category/column compatibility catalog for the current history page.
Latest vehicle telemetry
GET /api/v2/vehicles/{vin}/telemetry/latest
The path accepts the same VIN, plate or terminal identity resolution used by the vehicle service. After resolving identity once, the server reads GB32960, JT808 and YUTONG_MQTT independently and concurrently, at most five newest frames per protocol and 15 frames overall, without running pagination counts. It then returns the newest scalar value for every unified metric or remaining source field. A field that matches /api/v2/metrics.sourceFields uses the unified key, localized label, unit, type, description and category while retaining its exact sourceField. Manufacturer/protocol extensions remain visible under a server-owned dynamic category instead of being discarded or classified by the browser.
Every value includes protocol, optional sourceEndpoint, frameId, device and receive times, freshnessSeconds, optional dataDelaySeconds, and quality=good|stale|warning with an explicit qualityReason. good requires a successful parser status and a receive time within five minutes; stale means the newest value is older than five minutes; parse failures, missing/implausibly future receive times or device/receive divergence over five minutes are warning. The response also returns populated categories with counts, scannedFrames, staleAfterSeconds, asOf and selection evidence. Object/array fields are deliberately excluded from the compact latest-value surface and remain available through RAW evidence APIs.
Dashboard Summary
GET /api/dashboard/summary
V2 Global Monitor
GET /api/v2/monitor/summary?keyword=粤A&protocol=JT808&status=driving
GET /api/v2/monitor/map?zoom=5
GET /api/v2/monitor/map?zoom=13&bounds=113,22,114,24&status=online
summary and map share keyword, protocol and online|offline|driving|idle status semantics. map accepts bounds=minLongitude,minLatitude,maxLongitude,maxLatitude; malformed, non-finite, reversed or out-of-world bounds return MONITOR_BOUNDS_INVALID. Below zoom 11 the response remains clustered or mixed. At zoom 11 and above it returns lightweight MassMarks only when the visible point set is at most 2,000; otherwise it adaptively coarsens cells until no more than 2,000 clusters remain. It never returns full telemetry JSON in a point. The browser keeps the normal list at 200 rows, requests map data independently, waits 300ms after moveend/zoomend, and drops stale viewport bounds when a direct vehicle search must locate and focus its unique result.
Returns vehicle service KPIs, source distribution, vehicle service status distribution, link health, and realtime backlog.
Vehicle Service
GET /api/vehicle-service?keyword=粤AG18312
Returns one vehicle service view with identity, realtime summary, source coverage, history preview, RAW preview, mileage preview, and quality issues.
The response also carries profile, the supplemental business master record from vehicle_profile. Gateway-owned VIN, plate and OEM identity remain authoritative and are never overwritten by this record. Profile completeness is calculated across model, vehicle type, company, operation status, access provider, first access time and cumulative runtime.
GET /api/v2/vehicles/{vin}/profile
PUT /api/v2/vehicles/{vin}/profile
POST /api/v2/vehicle-profiles/sync
Profile reads require viewer; manual writes and bulk sync require admin. A manual write uses the returned version for optimistic concurrency, rejects unknown VINs and invalid status/time/runtime values, changes the source to manual, and creates a versioned vehicle_profile_audit snapshot. Supported operation statuses are unknown, active, inactive, maintenance, and retired.
The controlled sync endpoint accepts 1–500 complete supplemental profiles per request with required sourceSystem and sourceVersion, conflictPolicy=preserve|overwrite, and dryRun. VIN identity must already exist in the gateway-owned vehicle_identity_binding; missing identities are reported per item and never invented. The default preserve policy skips manual records and records owned by another external system. overwrite is an explicit administrator takeover. Repeating the same source/version and identical payload is unchanged; changing a payload under the same source/version is conflict_source_version, preventing a mutable upstream snapshot from silently defeating idempotency. Created and updated profiles receive source_system, source_version, synced_at, optimistic profile version, authenticated actor, and an immutable sync_created or sync_updated audit snapshot. dryRun=true uses the same identity, ownership, validation and locking path but rolls back all writes.
The admin UI accepts UTF-8 CSV with the exact header below, parses quoted fields and validates duplicate VIN, status and integer runtime before upload. It renders only aggregate counts plus the first 20 conflicts, so a 500-row batch does not create a large React table.
vin,modelName,vehicleType,companyName,operationStatus,accessProvider,firstAccessAt,runtimeSeconds
/api/vehicles/detail remains available as a compatibility alias. New UI and integrations should use /api/vehicle-service to make the vehicle-first boundary explicit.
sourceConsistency is the vehicle-level diagnosis across GB32960, JT808, and Yutong MQTT sources. The three protocols are source evidence for one vehicle service, so callers should display the diagnosis result instead of asking users to compare protocol rows manually.
{
"sourceConsistency": {
"scope": "detail",
"sourceCount": 3,
"onlineSourceCount": 2,
"locatedSourceCount": 2,
"missingProtocols": ["YUTONG_MQTT"],
"mileageDeltaKm": 0.8,
"sourceTimeDeltaSeconds": 42,
"status": "degraded",
"severity": "warning",
"title": "来源不完整",
"detail": "2/3 个来源在线,车辆服务可用但缺少 YUTONG_MQTT 来源。"
}
}
missingProtocols lists canonical source evidence that is completely absent for the vehicle service, so callers can distinguish a missing source from an offline-but-known source. status values are consistent, degraded, offline, single_source, no_source, mileage_divergent, and time_divergent. severity values are ok, warning, and error. /api/vehicle-service/overview and /api/vehicle-service/overviews also return sourceConsistency; overview responses may only include lightweight counts and diagnosis, while detail responses include location, mileage, and source-time deltas when realtime source rows are available.
Realtime Vehicles
GET /api/realtime/vehicles?keyword=粤AG18312&protocol=JT808&online=online&serviceStatus=degraded&limit=50&offset=0
Returns VIN-level realtime rows with canonical vehicle-level serviceStatus. Protocol is a source filter, not a product boundary.
Vehicle Coverage
GET /api/vehicles/coverage?keyword=粤AG18312&serviceStatus=degraded&limit=20&offset=0
Returns VIN-level source coverage rows for the vehicle service list. Each row includes the canonical vehicle-level serviceStatus so frontend, exports, and external integrations share the same health definition. serviceStatus accepts healthy, degraded, offline, no_data, and identity_required.
Coverage summary also exposes noDataVehicles, so UI can show vehicles that exist in identity binding but have no GB32960, JT808, or Yutong MQTT source evidence. /api/vehicles/coverage?serviceStatus=no_data returns those bound vehicles for follow-up source onboarding.
Coverage rows also include lightweight sourceConsistency so list views can show the same vehicle-level source diagnosis without issuing per-row detail requests.
History Locations
GET /api/history/locations?keyword=粤AG18312&protocol=JT808&dateFrom=2026-07-03%2000:00:00&dateTo=2026-07-03%2023:59:59&limit=20&offset=0
Returns historical location points from TDengine, enriched with plate where possible.
Location points include socPercent, directionDeg and alarmFlag when the source protocol supplies them. socAvailable distinguishes a real zero from an unavailable historical value. Device and receive timestamps are returned as absolute RFC3339 instants; query bounds must include an explicit offset (for example +08:00) so TDengine session timezone cannot shift the requested window.
For trajectory replay, the BFF should eventually return two shapes from the same evidence set:
items: paginated table rows for audit and export.mapPoints: bounded or sampled points for map rendering and playback.
This keeps table pagination precise while preventing large routes from overloading the browser map.
History Series And Controlled Export
GET /api/v2/history/series?keyword=粤AG18312&dateFrom=2026-07-14T00:00:00%2B08:00&dateTo=2026-07-14T06:00:00%2B08:00&targetPoints=240
POST /api/v2/exports
Content-Type: application/json
{
"keywords": ["粤AG18312"],
"category": "location",
"protocol": "JT808",
"dateFrom": "2026-07-14T00:00:00+08:00",
"dateTo": "2026-07-14T06:00:00+08:00",
"metrics": ["speed_kmh", "total_mileage_km"],
"format": "csv"
}
GET /api/v2/exports
GET /api/v2/exports/{id}/download
The series endpoint returns server-aggregated location telemetry with explicit bucket coverage and missingness. Requests are limited to 31 days and 60–600 target points; timestamps must identify an absolute window rather than relying on a database session timezone.
Export creation and listing require operator or admin. A task accepts 1–5 vehicles, location, raw, or mileage, CSV format, at most 31 days and 32 metrics. The service runs one export at a time and enforces a 1,000,000-row and 30-minute ceiling. Location and RAW data use stable forward-only cursors; the implementation does not use growing OFFSET scans.
Job status values are queued, running, completed, and failed. During execution, clients should display the server-owned processedRows, totalRows, progress, and evidence fields rather than estimate progress from elapsed time. On completion, rowCount, processedRows, and totalRows are equal, fileSizeBytes and completedAt are populated, and downloadUrl becomes available. The final CSV is exposed only after its temporary .part file has been flushed, synchronized, closed, and atomically renamed. Completed jobs survive API restarts; jobs interrupted while queued or running become explicit failures and may be recreated.
RAW Frames
Short GET query:
GET /api/history/raw-frames?keyword=粤AG18312&protocol=GB32960&limit=20&offset=0&includeFields=true
Large field-filter query:
POST /api/history/raw-frames/query
Content-Type: application/json
{
"keyword": "粤AG18312",
"protocol": "GB32960",
"dateFrom": "2026-07-03 00:00:00",
"dateTo": "2026-07-03 23:59:59",
"fields": ["gb32960.vehicle.speed_kmh", "gb32960.vehicle.total_mileage_km"],
"includeFields": true,
"limit": 20,
"offset": 0
}
Use POST when fields may be long. parsedFields is returned only when includeFields=true or specific fields are requested.
Mileage
GET /api/mileage/daily?keyword=粤AG18312&protocol=JT808&dateFrom=2026-07-01&dateTo=2026-07-03&limit=20&offset=0
GET /api/mileage/summary?keyword=粤AG18312&protocol=JT808&dateFrom=2026-07-01&dateTo=2026-07-03
Returns daily mileage rows and aggregate mileage summary.
The production projection table stores daily_mileage_km and latest_total_mileage_km; it deliberately no longer stores a duplicate first-total column. The API derives startMileageKm = latestTotalMileageKm - dailyMileageKm and normalizes a missing latest value to zero, matching the current gateway storage contract.
Alert Events
Current API surface:
GET /api/alert-events?keyword=粤AG18312&protocol=JT808&issueType=NO_SOURCE&limit=20&offset=0
GET /api/alert-events/summary?keyword=粤AG18312&protocol=JT808&issueType=NO_SOURCE
GET /api/alert-events/notification-plan?keyword=粤AG18312&protocol=JT808&issueType=NO_SOURCE&limit=20
/api/quality/issues, /api/quality/summary, and /api/quality/notification-plan remain compatibility aliases. New UI and integrations should use /api/alert-events* so the API language matches the vehicle data management center.
Alert event rows are vehicle-first and evidence-backed. Each event includes vehicle identity, issue type, severity, protocol/source when relevant, last seen time, source endpoint, detail, and links generated by the BFF/UI to realtime, trajectory, RAW, mileage, vehicle detail, or notification rule evidence.
Initial alert types:
NO_SOURCEVIN_MISSINGLINK_GAPFIELD_MISSINGCAPACITY_RISK
These legacy aliases remain read-only quality projections. The durable V2 alert center is a separate truth surface:
POST /api/v2/alerts/summary
POST /api/v2/alerts/events
GET /api/v2/alerts/events/{id}
POST /api/v2/alerts/events/{id}/actions
GET /api/v2/alerts/rules
POST /api/v2/alerts/rules
PUT /api/v2/alerts/rules/{id}
PUT /api/v2/alerts/rules/{id}/enabled
GET /api/v2/alerts/notifications?unreadOnly=true&limit=20&offset=0
POST /api/v2/alerts/notifications/read
Rule operators are type-aware: numeric metrics support gt/gte/lt/lte/eq/neq/between/outside; Boolean metrics support eq/neq/changed. Range rules carry threshold and thresholdHigh. Scopes support scopeProtocols, scopeVins, authoritative scopeOems, and scopeModels/scopeCompanies joined from vehicle_profile. Each scope list is capped at 500 normalized values with a 128-character per-value bound, protecting evaluator latency. Vehicles without the requested master-data dimension do not match a model/company-scoped rule. changed rules persist the previous Boolean state per rule/VIN/protocol, and repeat suppression uses repeatIntervalSec against the latest event fingerprint.
The rule editor reads its selectable metrics from GET /api/v2/metrics. The API repeats the same catalog validation on every create/update, so a stale or manipulated client cannot configure an unsupported evaluator field.
summary and events accept the same JSON filter: keyword, severity, status, ruleId, protocol, dateFrom, dateTo, limit, offset. Event states are unprocessed, processing, recovered, closed, ignored; event changes require the current version and actions are acknowledge, close, ignore. Every action writes an immutable timeline item with actor, before/after state, note, and timestamp. Stale event or rule writes return HTTP 409 with a *_VERSION_CONFLICT code.
Rules support numeric/Boolean values, bounded duration and repeat interval, recovery hysteresis, protocol/VIN scope, enable state, and a versioned audit snapshot. For telemetry metrics, duration is the difference between distinct, monotonic source event observations rather than time spent rereading one MySQL snapshot; freshness_sec is the explicit exception because staleness changes with platform time. Duplicate and late observations are counted but cannot advance a candidate. The evaluator deduplicates active rule+vehicle+protocol fingerprints and automatically recovers only when the configured recovery condition is true. Disabling a rule atomically clears its candidate and Boolean state together with the versioned disable audit. Station notifications have real unread/read state. sms, email, and wecom records are explicitly reserved; they are not reported as sent.
Online And Completeness Statistics
Planned API surface:
GET /api/statistics/online-rate?keyword=粤AG18312&dateFrom=2026-07-01&dateTo=2026-07-03&limit=20&offset=0
GET /api/statistics/completeness?keyword=粤AG18312&dateFrom=2026-07-01&dateTo=2026-07-03&limit=20&offset=0
GET /api/statistics/source-consistency?keyword=粤AG18312&dateFrom=2026-07-01&dateTo=2026-07-03&limit=20&offset=0
These APIs should link back to /api/history/locations or /api/history/raw-frames/query evidence. The UI must not show statistics as untraceable numbers.
V2 Access Management
POST /api/v2/access/summary
Content-Type: application/json
{"protocol":"JT808","onlineState":"offline","delayState":"abnormal"}
POST /api/v2/access/vehicles
Content-Type: application/json
{"keyword":"粤AG18312","protocol":"JT808","model":"ZK6122","provider":"G7s","firstSeenFrom":"2026-07-14T00:00:00+08:00","latestSeenFrom":"2026-07-14T08:00:00+08:00","limit":50,"offset":0}
POST /api/v2/access/unresolved-identities
Content-Type: application/json
{"keyword":"","protocol":"JT808","limit":20,"offset":0}
GET /api/v2/access/thresholds
PUT /api/v2/access/thresholds
summary and vehicles consume the same filter shape, so KPI, protocol/OEM distribution, and rows share one scope. Besides keyword, protocol, OEM and status, both accept substring filters model and provider, plus paired firstSeenFrom/firstSeenTo and latestSeenFrom/latestSeenTo bounds. A supplied pair must be ordered; malformed or reversed timestamps return ACCESS_TIME_INVALID or ACCESS_TIME_RANGE_INVALID. onlineState values are online, offline, never_reported, and unknown. Online state is calculated on every request from latest receive/update freshness and the current global/protocol threshold version; the stored snapshot boolean is not reused as the answer.
unresolved-identities returns terminals that have real access evidence but no authoritative VIN binding. The current implementation reads JT808 registration evidence and excludes terminals already present in vehicle_identity_binding. It exposes only a stable hashed ID and masked terminal identifier, never the raw phone number. Resolve missing_vin_jt808 by verifying registration evidence and maintaining an authoritative phone -> VIN binding; do not infer or fabricate a VIN.
Access rows expose both event and receive time, dataDelaySec, freshnessSec, the applied thresholdSec, latest realtime message semantics, event ID, source table, model/company master data, and evidence notes. The gateway snapshot upsert maintains access_first_seen_at, access_previous_received_at, access_latest_received_at, access_report_interval_ms, access_sample_count, and the latest received event ID independently from device event-time ordering. A strictly newer receipt with a different event ID advances the projection atomically; duplicates, equal timestamps, replay and older receipts do not change the interval or sample count. The API rounds milliseconds to reportIntervalSec and returns reportSampleCount, firstSeenSource, firstSeenEvidence, and reportIntervalEvidence. live_writer means first observation after the writer began maintaining the row. snapshot_backfill is only a deployment baseline reconstructed from the previous current-state row and must not be described as the vehicle's historical first-ever access.
Threshold configuration is stored in vehicle_access_threshold_config; every successful update increments version, requires the caller's previous version, and writes vehicle_access_threshold_audit. Stale updates return ACCESS_THRESHOLD_VERSION_CONFLICT. Valid ranges are bounded server-side.
Compatibility Rule
Existing callers may still send vin. The BFF treats vin as a vehicle lookup value for compatibility, so vin=粤AG18312 still resolves through identity binding. New code should use keyword to avoid implying the value is already a real VIN.
Track Replay Evidence
GET /api/v2/tracks accepts keyword|vin, optional protocol, dateFrom, dateTo and maxPoints.
- If either time bound is provided, both are required, the end must be later than the start, and the range is limited to seven days. Omitting both retains the bounded latest-slice compatibility mode.
- The TDengine source read remains capped at 5,000 latest points per request.
coverage.complete=falseandcoverage.evidenceexplicitly state when summary metrics describe only that slice rather than the whole requested window. coveragereports total, fetched, processed and returned point counts pluslatest_source_slice,quality_filterandmap_samplingreasons.qualityreports invalid coordinates, exact duplicates, short-interval >220 km/h implied drift, source switches and gaps over ten minutes. Filtered points never silently disappear without these counters.- When multiple protocols report the same VIN, coordinate quality is evaluated within each protocol and the map selects one primary source (explicit
protocol, otherwise highest point count then latest observation). Alternate valid points remain visible insources,quality.alternateSourcePoints, and theprimary_source_projectioncoverage reason; parallel source coordinates are never stitched into one route. segmentsare inferred asmoving,stoppedorgap. A stop requires at least three minutes of consecutive <=3 km/h and <=150 m interval movement. The evidence explicitly says this is GPS inference and not ignition state.- Positive subsecond intervals remain continuous telemetry even when their displayed whole-second duration rounds to zero; only invalid/non-increasing timestamps or intervals over ten minutes become
gap. - Sampling preserves the route endpoints, event points, stop centers and every retained segment boundary.
sampledIndexfields point directly into the returnedpointsarray; clients must not approximate an original index by ratio when this field is present. - Playback points and events carry synchronized speed, SOC availability/value, direction and alarm fields. The browser resolves only the paused/current coordinate through the cached reverse-geocode endpoint; playback never triggers one address request per point.