Logs API
Send structured log events. Requires a logs key. Accepts one event, an array, or { "logs": [...] } (batch up to 100).
/api/v1/logsFields
| Field | Type | Notes |
|---|---|---|
| level | string | debug · info · warn · error (default info) |
| name | string | Required. Event name, ≤ 200 chars |
| message | string | Optional human message, ≤ 4000 chars |
| metadata | object | Optional arbitrary JSON object |
| traceId | string | Optional correlation id, ≤ 100 chars |
| dataset | string | Optional dataset slug to send this event to. Must already exist. Defaults to 'default' |
Request
curl -X POST https://manage-staging.vecto3d.dev/api/v1/logs \
-H "Authorization: Bearer mng_your_logs_key" \
-H "Content-Type: application/json" \
-d '{"level":"info","name":"player.join","message":"Vextro joined","metadata":{"slot":12},"traceId":"sess-1"}'Datasets
Datasets are separate named streams of logs, created on the Datasetspage. A dataset can be restricted so only chosen members can read it (owners and admins always can) — that controls who can read it in the dashboard; sending is governed by holding the key.
Logs go to the default dataset unless you say otherwise, so you don't have to change anything to keep using logs as before. To target another dataset, set dataseton an entry, or at the top level to apply it to the whole batch. A per-entry value wins, so one request can carry several datasets. Unknown names are rejected with a 400 rather than being created, so a typo can't quietly make a junk dataset.
# one entry into "police"
curl -X POST https://manage-staging.vecto3d.dev/api/v1/logs \
-H "Authorization: Bearer mng_your_logs_key" \
-H "Content-Type: application/json" \
-d '{"dataset":"police","name":"evidence.saved","message":"Cam footage stored"}'
# a batch into "economy", with one entry overriding to "police"
curl -X POST https://manage-staging.vecto3d.dev/api/v1/logs \
-H "Authorization: Bearer mng_your_logs_key" \
-H "Content-Type: application/json" \
-d '{"dataset":"economy","logs":[{"name":"shop.sale"},{"name":"evidence.saved","dataset":"police"}]}'Response
{ "count": 1 }Errors
| Status | Meaning |
|---|---|
| 401 | Missing or invalid API key |
| 403 | Key is not a logs key |
| 400 | Invalid JSON or payload (e.g. missing name, > 1000 entries) |
| 413 | Request body larger than 8 MB |
| 400 | Unknown dataset — create it on the Datasets page first |
Errors that aren't from the API
Requests pass through a CDN edge before reaching Manage. If the edge rejects one, you get an HTML error page instead of our usual JSON. That's the quickest way to tell the two apart: if the body isn't JSON, the request never reached us, and it won't appear in your logs.
| Status | Cause | Fix |
|---|---|---|
| 501 | Your client sent a Transfer-Encoding header whose value isn't chunked. The edge refuses it as Not Implemented. | Don't set Transfer-Encoding yourself. Send a normal body with Content-Length, which is what every HTTP client does by default. |
| 405 | Wrong HTTP method — both endpoints are POST. | Use POST. |
Reading logs
To read logs, create a Logs (read) key on the API Keys page. Only owners and admins can create one, and you choose the datasets it can read. A read key can only read the datasets it was given; a Logs (write) key calling GET gets a 403.
/api/v1/logsAuthenticate with Authorization: Bearer mng_…. All parameters are optional.
Parameters
| Param | Type | Notes |
|---|---|---|
| q | string | search text, same as the dashboard box (phrase; -word / -"phrase" excludes) |
| filter | string | filter-language expression (license == "abc" AND level == "error") |
| level | string | debug · info · warn · error |
| dataset | string | one dataset slug; must be in the key's list, else 403 |
| since | ISO-8601 | instant; inclusive |
| until | ISO-8601 | instant; exclusive |
| limit | integer | 1-1000, default 100 |
| cursor | string | opaque, from a previous response's nextCursor |
Response
{
"logs": [
{ "id": "…", "createdAt": "2026-10-11T09:12:03.411Z", "level": "info",
"dataset": "admin", "name": "Admin Command", "message": "…",
"traceId": null, "metadata": { "license": "…" } }
],
"nextCursor": "…"
}Logs are returned newest first. nextCursor is null when there are no more results.
Paging
Pass nextCursor back as cursor until it's null. The cursor is keyset-based, so new logs never shift pages or cause duplicates and skips.
Errors
| Status | Meaning |
|---|---|
| 400 | invalid filter (the body carries the parser message and a `position` field), or invalid level / since / until / limit / cursor |
| 401 | Missing or unknown API key |
| 403 | Wrong key type; dataset outside the key's list; a read key with no datasets ("This key has no datasets - edit it on the API Keys page") |
| 429 | Rate limit, with Retry-After |
Request
curl -s "https://manage-staging.vecto3d.dev/api/v1/logs?filter=license%20%3D%3D%20%22abc%22&limit=50" \
-H "Authorization: Bearer $MANAGE_READ_KEY"From FiveM, call it server-side only and never ship a key to clients:
-- server-side only: never ship a key to clients
PerformHttpRequest(
"https://manage-staging.vecto3d.dev/api/v1/logs?dataset=admin&limit=20",
function(status, body)
if status ~= 200 then return print("read failed", status, body) end
for _, log in ipairs(json.decode(body).logs) do
print(log.createdAt, log.name, log.message)
end
end,
"GET", "", { ["Authorization"] = "Bearer " .. GetConvar("manage_read_key", "") }
)