Documentation

API

Drive the system from your own scripts: operations, permission scopes, exports, snapshots and events.

Everything you can drive from your own scripts: spawn and delete cars, seat players, control engine and lights, set paint and plates, apply upgrades, react to events. Lua and C# both work.

What each physics field means lives in Handling. Preparing a new car's assets lives in Model standard. Installation and permissions setup are in Getting started.

Ground rules

Cars are composite RDR objects with custom physics. The usual SetVehicle* and GetVehicle* natives do not touch them. Use these operations instead.

vehicleId is an opaque string, shaped session:7:43. It is not an entity handle and not a network ID, both of which the engine reuses. Restarting the core invalidates every previous ID, so never store one as a long-term key. For a garage, keep your own record and reapply state to a freshly spawned car.

seatId starts at 1, where 1 is the driver. playerId is a server player ID.

Exports take and return JSON strings, not Lua tables. Operation names are case-sensitive. Physics keys keep their capitals (EngineTorque); everything else is lower camelCase.

json
{"version":1,"id":"garage-1","op":"setEngine","vehicleId":"session:7:43","value":true}

Replies are {"id":"...","ok":true,"data":...} or {"id":"...","ok":false,"error":"..."}. Requests cap at 32 KiB. Duplicate keys, malformed JSON, unknown fields, numeric strings and non-finite numbers are all rejected. Player requests are rate-limited, so never call a setter every frame.

Calling from a client script

Request(json) answers get and list immediately from the local cache. Everything else queues a server request and returns {"ok":true,"pending":true,"id":"c123"}.

Pending is not success. Match the returned id against the local wvOnResponse event:

lua
local waiting = {}

AddEventHandler('wvOnResponse', function(raw)
    local reply = json.decode(raw)
    local done = waiting[reply.id]
    if done then waiting[reply.id] = nil; done(reply) end
end)

local function request(fields, done)
    fields.version = 1
    local queued = json.decode(exports.world_vehicles:Request(json.encode(fields)))
    if not queued.ok or not queued.pending then done(queued) return end
    waiting[queued.id] = done
end

request({op = 'delete', vehicleId = selected}, function(reply)
    if not reply.ok then print(reply.error) end
end)

Clear pending callbacks when your resource stops. Local getters are cached and scoped to that client, so they are not the whole server fleet and can be stale.

Calling from a server script

wvRequest(json) runs from a started server resource. Cfx authenticates the calling resource itself, so trusted management works without a player nearby and without the usual six-metre limit.

lua
local function wv(fields)
    fields.version = 1
    return json.decode(exports.world_vehicles:wvRequest(json.encode(fields)))
end

local plate = wv({op = 'setPlate', vehicleId = id, plateText = 'APPI 50', plateState = 'lemoyne'})
if not plate.ok then print(plate.error) end

local speed = wv({op = 'getSpeed', vehicleId = id})
if speed.ok and speed.data.available then
    print(('%.1f mph'):format(speed.data.speedMph))
end

Never forward untrusted network JSON straight into this export. A bridge like that hands a client server authority. Check your own garage, job and purchase rules first. Spawning, boarding and driver actions still require a real player.

The server export is registered once the core has finished starting, so a resource state of started is not proof that it is callable yet. Wrap the first call in pcall and retry later rather than spinning in a loop.

Who is allowed to do what

Scope Reach
Normal Creator or confirmed driver, same bucket, within 6 m. Deleting normally requires the creator.
Freeroam player Only while the mode is on, and only their own cars: spawn, paint, plate, delete. Same bucket, no 6 m rule for those actions, no admin anything.
Driver Confirmed seat 1, same bucket, within 5 m. Gear and horn.
Admin Developer ACE. Remote management of any car, across distance and buckets.
Trusted server A started resource calling the server export. No player needed; your code authorises the user.
Profile Shared baseline for one model and its future spawns.
Instance One car. Does not touch another and does not survive deletion or restart.

A trusted, developer or playerId field inside JSON grants nothing: trust comes from where the call originates. Identity, lifecycle, value validation and safe-editing checks still apply to admins. Seat tokens are private leases, not values to guess.

Reading state

Operation Input Returns
getApiInfo Protocol, capabilities, configured ACE, limits
listProfiles Available profile IDs; unavailable modules excluded
list Scoped snapshots; the server export lists every registered car
get id One snapshot. Normal reads are same-bucket and ≤120 m
getSpeed id speedMps, speedKmh, speedMph, available, source, measuredAt. Absolute 3D body speed including vertical movement, not wheel RPM. Use it only when available is true
getHandling id Full effective physics. A snapshot's handling holds overrides only
getHandlingSchema Every field with type, bounds, step, description, read-only flag
getTuning id Preset metadata and validity flags
getPaint id Supported flags, current preset IDs, revision, authored palettes
getPlate id Text, state, revision, available states
getPlateStates The five state styles with labels and colours
getAudioState id Engine and horn plus fresh telemetry when available
getDamage id Health, flooding, light failures, bumper states
getDamageSettings id Effective damage settings, not current HP
getSettings Session, revision, global settings, profile overrides
menuSnapshot The caller's own menu. access.role comes back as admin or freeroam; a player role gets their own cars, public model metadata and quota, never admin schemas or settings
adminSnapshot Whole fleet with location, bucket, speed, schemas. Admin only, not a per-frame poll

Use adminSnapshot to build an editor: it carries the schemas and read-only flags you need, and those schemas are your allowlist.

Cars and people

Operation Input Effect
spawn profile?, position?, heading? Issues a ticket, not a ready car. Wait for wvOnSpawn, then for the assembly flags to clear. Normal spawns are within 10 m of the player, and an explicit Z is used as given
delete id Removes one car and releases its occupants
flipVehicle id Admin. Assembled and stopped. Returns a pending token; completion arrives as wvOnFlip. Not a repair or a teleport
enter vehicleId?, seatId? Reserves a free seat within 5 m. No ID means the nearest car, no seat means the driver
seatReady id, token Completes an entering lease. Do not fake this
exit id, token Leaves the caller's seat. Moving, airborne and underwater exits are allowed
leave id, token Releases the lease outright, including emergency cleanup
setEngine id, value A damaged or flooded car will not start until repaired
setLights id, value A broken bulb group stays dark
setPanel id, panel, value Opens or closes an authored panel such as door_l or hood. It cannot create a hinge the model lacks
setHorn id, value Driver. Refresh roughly every 500 ms; it stops 1 s after the last refresh
setGear id, gear Driver. -1 for reverse, or 1 to the profile's gear count. No neutral or clutch
setTransmissionMode id, mode Admin, one car. Use global settings for fleet policy

A shared default horn recording ships with the core, so a model with no cue of its own still sounds; a pack or model cue overrides it. The sound fires once when the value goes from false to true rather than on every refresh, so release the horn before sounding it again.

Appearance

Operation Input Effect
setPaint id, paintId?, secondaryPaintId? At least one palette ID. Validated atomically
setPlate id, plateText?, plateState? At least one. Text is trimmed and uppercased: 1–8 characters, A–Z, 0–9, spaces and hyphens, with at least one letter or digit

Plate states are lemoyne warm white, new_hanover black, ambarino blue, west_elizabeth red and new_austin yellow. A model needs the matching assets for any of it to appear; authoring them is covered in Model standard.

A fresh car already wears a plate: five random digits in WV 48271 form and a random state, unless the profile sets plate.randomState to false or authors its own defaultText. That choice is made once and is not rerolled by a repair, a roster change or a new owner. Generated text avoids other registered cars, while text you set through the API does not have to be unique.

Selecting the value a car already has emits no event. Choices are instance state, so a garage stores them and reapplies them after a later spawn.

Damage

Operation Input Effect
repairVehicle id Admin. Body and engine to 1000, clears flooding. Does not start the engine
setHealth id, bodyHealth?, engineHealth? Admin. Absolute health 0–1000, not damage. Does not clear flooding
applyDamage id, bodyDamage?, engineDamage? Trusted server only. Subtracts health, never heals
setBumperDamage id, panel, stage Admin. bumper_f or bumper_r, stage 0–3: clean, dented, crushed, detached
setDamageSettings id, patch Admin. Per-instance settings, no HP change, not persisted

Damage settings accept enabled, collisionEnabled and waterEnabled as booleans; collisionMinDeltaV 0.1–100 and collisionMaxDeltaV 0.2–200 m/s with max above min; bodyDamagePerDeltaV 0–1000; engineDamageRatio 0–1; impactCooldownMs and waterConfirmMs 100–60000; submergedThreshold 0.1–1; and visualStage1Health, visualStage2Health, frontLightsFailureHealth, tailLightsFailureHealth 0–1000, with stage 2 at or below stage 1.

Tuning and editing

Operation Input Scope
setHandling id, patch Admin. Up to 64 raw overrides on one car
setSeat id, seatId, offset Admin. One car, each coordinate ±10 m
setEngineLevel id, level 0–4 Admin. One car
setTopSpeedMph id, mph 20–200 Admin. One car
setSuspensionFirmness id, value 0.5–2 Admin. One car
setSuspensionHeight id, meters −0.2–0.3 Admin. One car
setGlobalSettings patch, persist? Admin. Whole server
setProfileSettings profile, patch, persist? Admin. Every car of one model
exportProfileSettings profile, patch? Admin. Validates and writes a file without applying
exportHandling / exportSeats / exportDamageSettings / exportDiagnostics id Admin. Writes a file for review

Every tuning call needs the car assembled and stopped at ≤0.6 m/s, and the resulting complete physics must validate. Raw edits to a preset's fields invalidate that preset's metadata until you reapply it. What the values mean is in Handling.

Saving settings

persist:false, the default, applies a change for the session. persist:true applies it and saves the patch you just sent, merged with previously saved overrides. It does not sweep up unrelated live previews, so if you previewed something earlier and want it kept, send it again with persist:true.

The server writes two alternating journal files under the core resource's data/ directory, with checksums and readback verification, and can fall back to the last valid record. Clients never choose a path. A failed validation is not a save.

Exports only create files for you to review and install by hand. They never overwrite config.json or a module's vehicle.json.

Global settings cover freeroam, transmissionMode, speedometer, debug, controls, camera, audio and surfaceEffects. Profile settings cover physics, seats, seatExitOffsets, lighting, damage, engineInitiallyOn and audio. Authored strings such as audio.pack stay read-only.

debug holds five strict booleans, enabled, serverLogging, physicsDetails, printEverySecond and observerPoseEnabled, all false in a clean install. SetDebug moves only collection enabled; everything else goes through a setGlobalSettings patch, and none of it is a player privilege.

surfaceEffects holds enabled and dustPlumes, both true, and tireTracks, false, plus dustScale 0.25 to 3 with a default of 1.6, trackLifetimeSeconds 5 to 60 seconds with a default of 25, and trackOpacity 0.1 to 1 with a default of 0.55. Marks are cosmetic: they do not change traction, they do not deform terrain and they do not survive a restart.

Replies distinguish the live revision from the stored savedRevision. Saving an unchanged value can advance storage without emitting a change event.

A few things persistence deliberately is not: it is not a garage, it does not save cars, it does not eject current occupants, and engineInitiallyOn affects future spawns rather than the engine running right now.

Freeroam

Freeroam is a server-wide policy, off by default, that lets ordinary players open /wvmenu for their own cars. Switch it with setGlobalSettings and patch:{"freeroam":{"enabled":true}}, adding persist:true to keep it after a restart. It is a policy, not a role: it never grants the developer ACE and never opens an administrator action.

A player in that mode can spawn an available model, see what they have out, repaint it, renumber it and delete it. Handling, upgrades, repair, flip, global settings and diagnostics stay out of their menu, and the server validates every request anyway. A hidden button is not the security boundary.

Slots cap at three per player, or lower where limits.perPlayer already is. An active car, a pending ticket and an unfinished deletion each hold a slot, and quota reports active, pending, deleting, used, limit and canSpawn. The server-wide cap, the spawn cooldown and model availability still apply.

Ownership means the recorded creator, not the driver and not whoever happens to simulate the car. A passenger or a migrated network owner cannot repaint, renumber or delete someone else's car.

Turning the mode off cancels unfinished Freeroam spawn requests and closes the player menu on its next authoritative update. Cars that already exist stay, and an administrator's tools are untouched.

Named exports

The generic Request and wvRequest always work. Named wrappers just fill in op for you.

Client, answered locally: GetVehicle, ListVehicles, GetCurrentVehicle, GetApiInfo, GetAudioStatus, GetSurfaceEffectsStatus, GetSpeed, GetSpeedMph, GetSpeedKmh, wvGetTuning, wvGetPlateStates.

Client, queued: DeleteVehicle, FlipVehicle, wvGetPlate, wvSetPlate and the positional tuning setters. GetSpeedMph returning null means unavailable, not stopped.

Server: wvSpawnVehicle, wvDeleteVehicle, wvEnterVehicle, wvExitVehicle, wvGetVehicles, wvGetVehicle, wvListProfiles, wvGetHandling, wvGetHandlingSchema, wvGetAudioState, wvGetApiInfo, wvSetEngine, wvSetLights, wvSetPanel, wvPreviewHandling, wvPreviewSeat, wvExportHandling, wvExportSeats, wvExportDiagnostics, wvGetDamage, wvRepairVehicle, wvApplyDamage, wvSetHealth, wvSetBumperDamage, wvGetDamageSettings, wvPreviewDamageSettings, wvExportDamageSettings, wvGetPaint, wvSetPaint, wvGetPlate, wvSetPlate, wvGetSpeed, wvFlipVehicle, wvGetAdminSnapshot, wvGetSettings, wvSetGlobalSettings, wvSetProfileSettings, wvExportProfileSettings, wvGetPlateStates.

Both sides also expose positional tuning exports, which take arguments rather than JSON: wvGetTuning(id), wvSetEngineLevel(id, level), wvSetTopSpeedMph(id, mph), wvSetSuspensionFirmness(id, value), wvSetSuspensionHeight(id, meters).

C# SDK

Reference WorldVehicles.Api with the bundled Newtonsoft. CitizenFX pins a portable 12.0.0.0 assembly; do not swap in a modern .NET-only build.

csharp
var api = new WorldVehicles.Api.WorldVehiclesApiClient(
    json => Exports["world_vehicles"].wvRequest(json));   // server
    // client transport: json => Exports["world_vehicles"].Request(json)

api.SetPlate(vehicleId, "APPI 50", "lemoyne");
api.SetEngineLevel(vehicleId, 2);
api.SetGlobalTransmissionMode("manual", persist: true);
api.SetProfileHandling("pioneer_sedan", new JObject { ["RestLength"] = 0.70 }, true);

var speed = api.GetSpeed(vehicleId);

The SDK is a transport adapter, not an entity owner and not a permission bypass. Client helpers still queue rather than answering synchronously. VehicleSpeedState is nullable, so check Available first.

Methods mirror the operations above, plus convenience wrappers: SetGlobalTransmissionMode, SetSpeedometer, SetDebug, SetProfileHandling, and Request(op, vehicleId, fields) for anything else.

Snapshots

A snapshot carries id, net, generation, profile, model, creator, seats, preparing, spawnFrozen, engine and horn and light state, panels, handling and its revision, seat offsets, transmission mode, requested gear, tuning, telemetry, paint and plate state with revisions, and damage with settings.

Local snapshots add entity, effectiveHandling, telemetryAvailable and the viewer's own localSeatId, localSeatToken and localSeatPhase. An entity of 0 means the body is not resolved on that client this frame.

Change things through the API, not by editing a returned snapshot. A reserved seat may still be entering: wvOnEnter is what confirms it is ready.

Events

Subscribe with AddEventHandler in Lua or EventHandlers in C#. These are local notifications, not commands to send back.

Event Arguments
wvOnSpawn vehicleId
wvOnEnter vehicleId, seatId, playerId
wvOnExit vehicleId, seatId, playerId
wvOnDelete vehicleId
wvEngine vehicleId, enabled, actorId
wvOnLights vehicleId, enabled, actorId
wvOnPanel vehicleId, panelId, open, actorId
wvOnHorn vehicleId, enabled, actorId
wvOnPaint vehicleId, paintStateJson, actorId
wvOnPlate vehicleId, plateStateJson, actorId
wvOnTuningChanged vehicleId, tuningJson, actorId
wvOnDamage vehicleId, damageJson, reason
wvOnHealthSet vehicleId, damageJson
wvOnRepair vehicleId, damageJson
wvOnBumperImpact vehicleId, panelId, deltaV, bumperStateJson
wvOnFlip vehicleId, success, actorId
wvOnSettingsChanged settingsJson, actorId
wvOnProfileSettingsChanged profileId, profileJson, actorId
wvOnResponse replyJson

Four practical notes. Every ...Json argument is a string, so decode it. wvOnDamage carries a reason of collision, flood or manual. An actorId of 0 means a trusted server or system action. wvOnExit also fires for a cancelled entry, so handle it idempotently.

Vehicle events are server-local and relayed to clients in that car's bucket. A remote admin should query fleet state rather than assume every cross-bucket event arrives. The settings events are server-local too, so do not treat them as a broadcast channel to clients.

Internal wv:* streams carry tickets, seat leases and owner epochs. They are not an alternative public API.

Not available

No arbitrary vertex deformation, no bullet tyre punctures, no persistent tyre tracks, no full door damage, no collision-mesh editing and no arbitrary RGB paint setter. Authored bumper stages and health-driven light failures do not imply otherwise. The radio is a separate add-on, and the only menu an ordinary player can open is the reduced Freeroam one.

Building a garage

The usual integration, and the shape that avoids every trap above:

  1. Add dependency 'world_vehicles' to your resource.
  2. Keep your own stable record per owned car: model, plate, colours, upgrades.
  3. When a player retrieves it, spawn a fresh car and wait for wvOnSpawn plus the assembly flags.
  4. Reapply plate, paint and upgrades from your record.
  5. On storage, read what you need and delete.

Never persist a vehicleId between sessions, and remember that configuration persistence is not this database.