Skip to main content

Track staff time with the game API (Luau example)

A complete Roblox Luau script that starts and ends activity sessions through the public API, with the notes you need to run it safely.

Written by Liam Radford

You can track activity either with the downloadable activity loader (see "Set up activity tracking in your game (the loader, rank and idle time)") or by writing your own script against this API. Use this article if you are writing your own.

The downloadable loader does not use this API. It calls older /api/activity endpoints with a separate activity key. Keys made under Settings > Public API do not work with the loader, and the other way round.

Before you start

  1. In Roblox Game Settings turn on Security > Allow HTTP Requests.

  2. Create an API key (see "The Rosterr game API: what it is, how to make an API key and keep it safe").

  3. Put the key in a server-only place, such as a Secret or a module under ServerScriptService.

The script

Put this in a Script in ServerScriptService:

local HttpService = game:GetService("HttpService")
local Players = game:GetService("Players")local BASE_URL = "https://YOUR-ROSTERR-ADDRESS/api/public/v1/workspace/" .. "YOUR_GROUP_ID"
local API_KEY = "rosterr_..." -- keep server-side onlylocal MIN_RANK = 1 -- only track players at or above this group rank
local GROUP_ID = tonumber("YOUR_GROUP_ID")local function request(method: string, path: string, body: { [string]: any }?)
	local ok, result = pcall(function()
		return HttpService:RequestAsync({
			Url = BASE_URL .. path,
			Method = method,
			Headers = {
				["Authorization"] = "Bearer " .. API_KEY,
				["Content-Type"] = "application/json",
			},
			Body = body and HttpService:JSONEncode(body) or nil,
		})
	end)	if not ok then
		warn("[Rosterr] request failed:", result)
		return nil
	end	if result.StatusCode == 429 then
		warn("[Rosterr] rate limited; retry after", result.Headers["retry-after"])
	end	local decoded = (result.Body ~= "" and select(2, pcall(HttpService.JSONDecode, HttpService, result.Body))) or nil
	return result.StatusCode, decoded
endlocal function nowIso(): string
	return DateTime.now():ToIsoDate() -- UTC, e.g. 2026-09-19T18:04:05Z
endPlayers.PlayerAdded:Connect(function(player)
	if player:GetRankInGroup(GROUP_ID) < MIN_RANK then return end
	local status, body = request("POST", ("/user/%d/activity/start"):format(player.UserId), { time = nowIso() })
	-- 404: not a workspace member; 409: session already open (e.g. server hop). Both are safe to ignore.
	if status and status >= 400 and status ~= 404 and status ~= 409 then
		warn("[Rosterr] start failed:", status, body and body.error)
	end
end)Players.PlayerRemoving:Connect(function(player)
	if player:GetRankInGroup(GROUP_ID) < MIN_RANK then return end
	request("POST", ("/user/%d/activity/end"):format(player.UserId), { time = nowIso() })
end)game:BindToClose(function()
	for _, player in Players:GetPlayers() do
		request("POST", ("/user/%d/activity/end"):format(player.UserId), { time = nowIso() })
	end
end)

How it works

  • Start: POST /user/{userId}/activity/start with { "time": "<UTC ISO 8601>" }. You get 409 if a session is already active for that person.

  • End: POST /user/{userId}/activity/end with the same body. You get 404 if no session is active.

  • userId is the Roblox user ID and must belong to a member of the workspace (otherwise 404).

  • Times are UTC in ISO 8601 format.

Notes

  • RequestAsync (not GetAsync or PostAsync) is used so you can read the status code and headers instead of non-2xx responses throwing errors.

  • Roblox limits each server to 500 HTTP requests a minute, and the API allows 200 per key per 5 minutes. So only track staff ranks, and avoid sending anything per chat message.

  • If a server shuts down, BindToClose ends open sessions so time is not lost.

  • Roblox Studio is not a real server. Test in a published game.

Related articles

  • The Rosterr game API: what it is, how to make an API key and keep it safe

  • Rosterr game API: endpoints reference

  • Game API errors and rate limits (401, 404, 409, 429 and how to back off)

Did this answer your question?