Every docs URL answers with Markdown when you add .md. Site map: llms.txt
Public API
Fetch our GTA V datasets, search the docs, resolve a joaat hash and call our flag calculators over plain HTTP, with examples in Lua, JavaScript, Python and curl.
Bu dokümanlar şimdilik İngilizce.
Every dataset and every calculation behind the fivemad tools is reachable over HTTP. There is no key, no account and no quota to apply for. Every endpoint is a plain GET with query parameters, so anything that can fetch a URL can use it: a FiveM resource, a build script, your editor, a browser tab, or an assistant you pointed at llms.txt.
If what you actually want is for ChatGPT, Claude or Cursor to read our documentation, that is the other page: using fivemad with an AI assistant.
| What | Where |
|---|---|
| Every dataset, with its row shape and size | /data/index.json |
| Search tools, docs and datasets | /api/search?q= |
| joaat hash, both directions | /api/hash?q= |
| The calculators | /api/tools |
Calling it
One call, four ways. Everything after this section uses the shortest form that makes the point, because the request is always just a URL.
Lua, inside a resource
PerformHttpRequest is a server side native, so this belongs in a server script. The callback gets (status, body, headers) where status is the HTTP status code (or 0 when the request never got out), and body is the raw response text, so it needs json.decode.
local API = "https://fivemad.com/api"
--- Look up a joaat hash or a name. cb(result) gets one entry of `results`, or nil.
local function lookupHash(query, cb)
local url = ("%s/hash?q=%s"):format(API, query)
PerformHttpRequest(url, function(status, body, headers)
if status ~= 200 then
print(("[fivemad] lookup failed, HTTP %s"):format(status))
return cb(nil)
end
local data = json.decode(body)
cb(data and data.results and data.results[1] or nil)
end, "GET")
end
RegisterCommand("modelname", function(source, args)
lookupHash(args[1] or "", function(r)
if not r then return end
print(("[fivemad] %s -> %d match(es)"):format(r.query, r.matchCount))
for _, m in ipairs(r.matches) do
print((" %s %s"):format(m.name, m.kindLabel))
end
end)
end, true)modelname 0xB779A091 in the server console:
[fivemad] 0xB779A091 -> 1 match(es)
adder Vehicle modelWarning
Call this from a command, an admin tool or a one off script, and cache what you get. Do not put it in a tick, a loop over every entity or anything that runs per player: it is a documentation API, not a game service, and the rate limits below are real. If you need a lookup on the hot path, fetch the dataset once at resource start and keep the table in memory.
JavaScript, in the browser or in Node
The /api/* endpoints send access-control-allow-origin: *, so this works from a page as well as from Node.
const res = await fetch("https://fivemad.com/api/hash?q=0xB779A091");
const data = await res.json();
for (const m of data.results[0].matches) console.log(m.name, m.kind);adder vehiclePython
Nothing to install; the standard library is enough.
import json, urllib.request
with urllib.request.urlopen("https://fivemad.com/api/hash?q=0xB779A091") as r:
data = json.load(r)
for m in data["results"][0]["matches"]:
print(m["name"], m["kind"])adder vehiclecurl
curl "https://fivemad.com/api/hash?q=0xB779A091"All four get the same answer:
{
"query": "0xB779A091",
"searched": { "names": 113305, "sets": 22 },
"note": "joaat is a 32 bit hash, so a value can belong to more than one name. Every match is listed; none is picked for you...",
"limit": "60 requests per minute per IP",
"count": 1,
"results": [
{
"query": "0xB779A091",
"readAs": "hash",
"hash": { "unsigned": 3078201489, "signed": -1216765807, "hex": "0xB779A091" },
"matchCount": 1,
"matches": [
{
"name": "adder",
"kind": "vehicle",
"kindLabel": "Vehicle model",
"page": "https://fivemad.com/tools/model-hash-browser?q=adder"
}
]
}
]
}The datasets
The reference data behind the tools is static JSON under /data. It is the same file the tool itself fetches, so nothing is held back.
Start with the index, which is generated from the real files:
curl https://fivemad.com/data/index.json{
"site": "https://fivemad.com",
"generated": "2026-10-18",
"docs": "https://fivemad.com/docs/ai/api",
"llms": "https://fivemad.com/llms.txt",
"count": 34,
"totalBytes": 16420844,
"datasets": [
{
"id": "props",
"title": "Props with pictures",
"url": "https://fivemad.com/data/props.json",
"about": "Every GTA V prop model that has a picture, with a readable label, category, tags and its real size in metres.",
"shape": "rows: [model, label, category, tags, [x,y,z] size in m, minBuild, hasThumb, has3d, hasShot]",
"rows": 30033,
"bytes": 2713710,
"size": "2.6 MB",
"page": "https://fivemad.com/tools/objects-list",
"pageName": "Objects / Props List"
}
]
}Each entry tells you the URL, what the file holds, how a row is laid out, how many rows there are and how big the download is. The rows are short arrays on purpose: these files are fetched by browsers, so the column names live in the index and in a cols field inside the file, not on every row.
Some of these files are several megabytes, so download one and keep it rather than refetching. If all you need is a single lookup, the search and hash endpoints below answer in a few hundred bytes.
Loading a dataset once at resource start is the right pattern for a server:
local props = nil
CreateThread(function()
PerformHttpRequest("https://fivemad.com/data/props.json", function(status, body)
if status ~= 200 then return end
props = json.decode(body).rows -- 30,033 rows, about 2.6 MB
print(("[fivemad] %d props loaded"):format(#props))
end, "GET")
end)Note
A 2.6 MB download on every server start is fine once; on a server you restart often, ship the file with your resource instead and update it when you feel like it. These files change when we rebuild, not per request.
Search
One short JSON answer over the tool list, the documentation and the dataset index. This is the cheapest way for a script or an assistant to find out what we have without downloading anything.
curl "https://fivemad.com/api/search?q=handling&limit=3"{
"query": "handling",
"type": "all",
"total": 18,
"returned": 3,
"limit": "60 requests per minute per IP",
"hint": "Add .md to any docs URL for the plain Markdown source.",
"results": [
{
"type": "doc",
"title": "Vehicle Handling Editor",
"url": "https://fivemad.com/docs/tools/vehicle-handling-editor",
"markdown": "https://fivemad.com/docs/tools/vehicle-handling-editor.md",
"summary": "Tune CHandlingData with sliders, import an existing handling.meta, toggle flag bits, and export handling.meta, runtime Lua or a carcols.meta mod kit.",
"extra": {
"section": "Tools",
"match": "The Vehicle Handling Editor lets you tune a vehicle's CHandlingData block with sliders and a one line explanation per field..."
},
"score": 117.5
},
{
"type": "tool",
"title": "Vehicle Handling Editor",
"url": "https://fivemad.com/tools/vehicle-handling-editor",
"summary": "Tune mass, drive force, grip and suspension from a UI, then export handling.meta or runtime Lua.",
"extra": { "category": "Config & Code", "tags": ["Vehicles", "Download"] },
"score": 110
}
]
}Parameters: q (required), type (all, tools, docs, datasets), limit (1 to 50, default 10). Every documentation hit carries its .md URL, so the next step is one more fetch. The score only explains the order; do not read anything into the absolute number.
Look up a joaat hash
Paste a hash and get the name back. The search runs over every name set the site ships: props and map objects, vehicles, peds, weapons and their components, animation dictionaries and clips, particle assets and effects, sounds and sound sets, ymap and IPL names, MLO interiors, scaleform movies, ped decorations, timecycle modifiers, animpostfx effects, player stats and cutscenes. That is 113,305 names in 22 kinds today, and searched.byKind in every answer lists them with their counts.
The full response is in Calling it above. It works the other way round too: pass a name and you get the unsigned, signed and hex forms plus the Lua backtick literal, and the match list says whether the game really ships that name.
curl "https://fivemad.com/api/hash?q=adder"{
"results": [
{
"query": "adder",
"readAs": "name",
"name": "adder",
"lua": "`adder`",
"hash": { "unsigned": 3078201489, "signed": -1216765807, "hex": "0xB779A091" },
"matchCount": 1,
"matches": [{ "name": "adder", "kind": "vehicle", "kindLabel": "Vehicle model" }]
}
]
}qaccepts an unsigned value, a signed int32,0xhex, bare 8 digit hex, or a name.- Up to 25 values in one call, separated by commas or new lines.
kindnarrows the answer, for examplekind=vehicle,ped.- A match may carry a
parent: one animation dictionary, particle asset or sound set that contains the name. Because the hash depends on the name alone, a name is stored once, so the same clip or effect often lives in other dictionaries too. Treatparentas an example, not as the whole answer.
Important
joaat is a 32 bit hash, so a value can belong to more than one name. Every match is returned as a list and none of them is picked for you. If matchCount is above 1, the answer is genuinely ambiguous and you have to decide from context.
Here is a real one. 0x37D80B0E is both a prop model and a ymap of the same name, so both come back:
curl "https://fivemad.com/api/hash?q=0x37D80B0E"{
"results": [
{
"query": "0x37D80B0E",
"readAs": "hash",
"matchCount": 2,
"matches": [
{ "name": "prop_cheetah_covered", "kind": "prop", "kindLabel": "Prop / object model" },
{ "name": "prop_cheetah_covered", "kind": "ymap", "kindLabel": "ymap / IPL name" }
]
}
]
}A name we do not ship comes back with matchCount: 0. That means we have no record of it, not that it does not exist.
The page for the same thing is the joaat Hash Calculator, under the “Reverse lookup” tab.
Call a tool
The parts of the toolkit that are a pure function of their input can be called directly, so neither you nor an assistant has to work out a flag value by hand. They run the same code as the tool pages, so the two can never give different answers.
| Endpoint | What it does |
|---|---|
/api/tools/joaat |
joaat hash of one or more strings |
/api/tools/driving-style |
driving style flags for TaskVehicleDriveToCoord and friends |
/api/tools/animation-flags |
the flag argument of TaskPlayAnim |
/api/tools/warning-buttons |
button flags for SetWarningMessage |
/api/tools/handling-flags |
strHandlingFlags, strModelFlags and strDamageFlags |
The index gives a working example URL for each one, so the whole thing is discoverable from a single fetch:
curl https://fivemad.com/api/tools{
"about": "Calculators from the fivemad toolkit, callable directly. GET with query parameters or POST a JSON object with the same keys. Nothing that needs a file upload, a GPU or the game files is here.",
"docs": "https://fivemad.com/docs/ai/api",
"limit": "120 requests per minute per IP, 240 across the whole API",
"count": 5,
"tools": [
{
"id": "driving-style",
"name": "Driving style flags",
"about": "eVehicleDrivingFlags for TaskVehicleDriveToCoord, TaskVehicleDriveWander and SetDriveTaskDrivingStyle: decode a value or build one from flag names.",
"url": "https://fivemad.com/api/tools/driving-style",
"page": "https://fivemad.com/tools/driving-style-calculator",
"params": [
{ "name": "value", "about": "Decode this value: 786603, -1073741824, 0xC00AB, 0b1011 or 1|2|128." },
{ "name": "flags", "about": "Encode instead: flag names separated by commas (StopForVehicles, StopForPeds, ...)." },
{ "name": "preset", "about": "One of: Normal, Strict, Ignore lights, Avoid vehicles, Reckless, ..." }
],
"example": "https://fivemad.com/api/tools/driving-style?value=786603"
}
]
}Decode a value
curl "https://fivemad.com/api/tools/driving-style?value=786603"{
"tool": "driving-style",
"name": "Driving style flags",
"page": "https://fivemad.com/tools/driving-style-calculator",
"limit": "120 requests per minute per IP",
"value": 786603,
"signed": 786603,
"hex": "0x000C00AB",
"binary": "0000 0000 0000 1100 0000 0000 1010 1011",
"active": [
{ "bit": 0, "value": 1, "name": "StopForVehicles", "label": "Stop for vehicles", "desc": "Brakes and waits behind vehicles in its way instead of driving into them.", "unknown": false },
{ "bit": 1, "value": 2, "name": "StopForPeds", "label": "Stop for peds", "desc": "Stops for pedestrians on the road.", "unknown": false },
{ "bit": 3, "value": 8, "name": "SteerAroundStationaryVehicles", "label": "Steer around stationary vehicles", "desc": "Steers around parked and empty vehicles.", "unknown": false },
{ "bit": 5, "value": 32, "name": "SteerAroundObjects", "label": "Steer around objects", "desc": "Steers around props and objects on the road.", "unknown": false },
{ "bit": 7, "value": 128, "name": "StopAtTrafficLights", "label": "Stop at traffic lights", "desc": "Obeys red lights and stop lines.", "unknown": false },
{ "bit": 18, "value": 262144, "name": "UseShortCutLinks", "label": "Use shortcut links", "desc": "Allows shortcut road links such as dirt tracks and alleys. Removes most route limits.", "unknown": false },
{ "bit": 19, "value": 524288, "name": "ChangeLanesAroundObstructions", "label": "Change lanes around obstructions", "desc": "Changes lanes to pass slower traffic and obstructions.", "unknown": false }
],
"expression": "StopForVehicles | StopForPeds | SteerAroundStationaryVehicles | SteerAroundObjects | StopAtTrafficLights | UseShortCutLinks | ChangeLanesAroundObstructions",
"natives": ["TaskVehicleDriveToCoord", "TaskVehicleDriveToCoordLongrange", "TaskVehicleDriveWander", "SetDriveTaskDrivingStyle"],
"valuesUsedByRockstar": [0, 1, 4, 16, 131, 139, 7785, 7791, 262144, 262208, 262275, 525116, 786468]
}Build one from flag names
The answer has the same shape, so you can round trip it:
curl "https://fivemad.com/api/tools/driving-style?flags=StopForVehicles,StopAtTrafficLights,SteerAroundPeds"{
"value": 145,
"signed": 145,
"hex": "0x00000091",
"binary": "0000 0000 0000 0000 0000 0000 1001 0001",
"expression": "StopForVehicles | SteerAroundPeds | StopAtTrafficLights"
}In Python, to build the value for a config file:
import json, urllib.parse, urllib.request
flags = ["StopForVehicles", "StopAtTrafficLights", "SteerAroundPeds"]
url = "https://fivemad.com/api/tools/driving-style?" + urllib.parse.urlencode({"flags": ",".join(flags)})
with urllib.request.urlopen(url) as r:
data = json.load(r)
print(data["value"], data["expression"])145 StopForVehicles | SteerAroundPeds | StopAtTrafficLightsHashing, in bulk
curl "https://fivemad.com/api/tools/joaat?text=adder,WEAPON_PISTOL"{
"tool": "joaat",
"note": "The game lower-cases a string before hashing it, so ADDER and adder give the same value.",
"results": [
{ "input": "adder", "unsigned": 3078201489, "signed": -1216765807, "hex": "0xB779A091", "lua": "`adder`" },
{ "input": "WEAPON_PISTOL", "unsigned": 453432689, "signed": 453432689, "hex": "0x1B06D571", "lua": "`weapon_pistol`" }
]
}handling.meta flags
handling.meta writes its flag fields as bare hex, so that is how this endpoint reads a value, and it gives you the line back ready to paste:
curl "https://fivemad.com/api/tools/handling-flags?set=strHandlingFlags&value=440010"{
"set": "strHandlingFlags",
"label": "Handling flags",
"value": 4456464,
"hex": "440010",
"metaValue": "<strHandlingFlags value=\"440010\" />",
"binary": "0000 0000 0100 0100 0000 0000 0001 0000",
"active": [
{ "bit": 4, "value": 16, "name": "HF_NO_HANDBRAKE", "desc": "Handbrake disabled." },
{ "bit": 18, "value": 262144, "name": "HF_REDUCED_DRIVE_OVER_DAMAGE", "desc": "Takes less damage driving over things." },
{ "bit": 22, "value": 4194304, "name": "HF_TYRES_RAISE_SIDE_IMPACT_THRESHOLD", "desc": "Tyres resist side impacts better." }
],
"expression": "HF_NO_HANDBRAKE | HF_REDUCED_DRIVE_OVER_DAMAGE | HF_TYRES_RAISE_SIDE_IMPACT_THRESHOLD"
}Discovering the flags
Call any of these with no parameters and it answers with its whole flag list, its presets and what each bit means, which is enough for an assistant to pick the right flags on its own:
curl "https://fivemad.com/api/tools/animation-flags"POST with a JSON body works the same as the query string, if that suits your client better:
curl -X POST https://fivemad.com/api/tools/animation-flags \
-H "content-type: application/json" \
-d '{"flags":"AF_LOOPING,AF_UPPERBODY"}'Rate limits
Per IP, in a fixed one minute window:
| Endpoint | Limit |
|---|---|
/api/search |
60 per minute |
/api/hash |
60 per minute |
/api/tools and /api/tools/* |
120 per minute |
| All of the above together | 240 per minute |
Over the limit you get 429 with a retry-after header in seconds and a JSON body that says which limit you hit:
{
"error": "rate_limited",
"message": "Too many requests. This endpoint allows 60 requests per minute per IP (240 across the whole API). Try again in 41s.",
"docs": "https://fivemad.com/docs/ai/api"
}The static files (/data/*.json, the .md pages, llms.txt) are served straight off disk and are not rate limited. Please cache them rather than refetching.
The older endpoints keep their own limits: /api/server allows 30 lookups per minute per IP and /api/blacklist allows 20.
CORS, caching and errors
- The
/api/*endpoints answer withaccess-control-allow-origin: *and handle a preflight, so a browser based agent can call them from any page. - The static files (
/data/*.json,/data/index.json, the.mdpages,llms.txt) are served by the front end web server, not by the app, so whether they carry that header depends on the deployment. From a server side script,PerformHttpRequest, curl or an assistant’s own fetcher this makes no difference. If you are calling them from a browser and get a CORS error, use/api/searchor/api/hashinstead, or proxy the file yourself. - Errors are JSON with an
errorcode, amessagewritten for a human, and a link back to this page. Status codes are the ordinary ones:400bad input,404unknown tool,429rate limited,503when an index is not loaded yet.
curl "https://fivemad.com/api/hash"{
"error": "invalid",
"message": "Pass ?q= a joaat hash (3078201489, -1216765807 or 0xB779A091) or a name (adder). Up to 25 values, separated by commas or new lines.",
"docs": "https://fivemad.com/docs/ai/api"
}/api/*answers are cached for 60 seconds. The static files carry a longer cache.
A worked example
“What driving style should I use for an ambulance that runs red lights but still avoids traffic?”
# 1. see what the flags mean
curl "https://fivemad.com/api/tools/driving-style"
# 2. build the value
curl "https://fivemad.com/api/tools/driving-style?flags=SwerveAroundAllVehicles,SteerAroundStationaryVehicles,SteerAroundPeds,SteerAroundObjects,AllowGoingWrongWay"
# 3. check the vehicle model exists and get its hash
curl "https://fivemad.com/api/hash?q=ambulance"Step 2 answers:
{
"value": 572,
"signed": 572,
"hex": "0x0000023C",
"expression": "SwerveAroundAllVehicles | SteerAroundStationaryVehicles | SteerAroundPeds | SteerAroundObjects | AllowGoingWrongWay",
"natives": ["TaskVehicleDriveToCoord", "TaskVehicleDriveToCoordLongrange", "TaskVehicleDriveWander", "SetDriveTaskDrivingStyle"]
}and step 3 confirms ambulance is a real model (0x45D56ADA), so the call writes itself:
-- 572 = SwerveAroundAllVehicles | SteerAroundStationaryVehicles | SteerAroundPeds
-- | SteerAroundObjects | AllowGoingWrongWay
TaskVehicleDriveToCoord(ped, vehicle, x, y, z, 25.0, 0, `ambulance`, 572, 8.0, 1.0)This is the whole point of the tool endpoints: the flag value and the meaning of each bit come back together, so nothing has to be remembered or guessed. The same three calls are what an assistant makes when you ask it the same question, which is covered on the assistant page.
