Manage

Logs API

Send structured log events. Requires a logs key. Accepts one event, an array, or { "logs": [...] } (batch up to 100).

POST/api/v1/logs

Fields

FieldTypeNotes
levelstringdebug · info · warn · error (default info)
namestringRequired. Event name, ≤ 200 chars
messagestringOptional human message, ≤ 4000 chars
metadataobjectOptional arbitrary JSON object
traceIdstringOptional correlation id, ≤ 100 chars
datasetstringOptional dataset slug to send this event to. Must already exist. Defaults to 'default'

Request

bash
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.

bash
# 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

json
{ "count": 1 }

Errors

StatusMeaning
401Missing or invalid API key
403Key is not a logs key
400Invalid JSON or payload (e.g. missing name, > 1000 entries)
413Request body larger than 8 MB
400Unknown 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.

StatusCauseFix
501Your 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.
405Wrong 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.

GET/api/v1/logs

Authenticate with Authorization: Bearer mng_…. All parameters are optional.

Parameters

ParamTypeNotes
qstringsearch text, same as the dashboard box (phrase; -word / -"phrase" excludes)
filterstringfilter-language expression (license == "abc" AND level == "error")
levelstringdebug · info · warn · error
datasetstringone dataset slug; must be in the key's list, else 403
sinceISO-8601instant; inclusive
untilISO-8601instant; exclusive
limitinteger1-1000, default 100
cursorstringopaque, from a previous response's nextCursor

Response

json
{
  "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

StatusMeaning
400invalid filter (the body carries the parser message and a `position` field), or invalid level / since / until / limit / cursor
401Missing or unknown API key
403Wrong 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")
429Rate limit, with Retry-After

Request

bash
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:

lua
-- 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", "") }
)