Browser route
- Added
- Updated
allowedAddresses (this machine only out of the box) and the token.Reach the panel from an address you have allowed. Any one of these works:
- Reach it over a VPN (Tailscale, WireGuard)
- Reach it through an SSH port forward
- Put a TLS reverse proxy in front of it (see
trustedProxies) - Only reach it from the same LAN
If you are on shared hosting and can do none of these, leave the browser route off and use the in-game screen.
Enabling it
config.web.http.enabled = true
Tokens come from convars, and one pair covers every RE : CORD panel at once:
# every RE:CORD panel
set REC:adminToken "a-random-string-of-32-chars-or-more"
set REC:supportToken "another-one-for-read-only-access"
# this panel only, overriding the shared one
set REC_ItemManager:supportToken "a-third-one"
48 characters, generated in your browser. Nothing is sent anywhere.
adminToken carries *, supportToken carries read. An empty token makes every /api/* call return 401. A token shorter than 32 characters prints a warning on startup.
For something narrower than those two, add a named token and give it its own convar:
customTokens = {
{ name = "itemsOnly", scopes = { "items" }, },
}
set REC_ItemManager:token:itemsOnly "yet-another-random-string"
rec_web_audit with the token label behind it. That table ships with REC_Library as well. Without it the trail stays in memory and a warning is printed on startup.allowedAddresses
Requests from an address that is not on this list get 404 — before authentication, so the route stays invisible from the outside. The default allows this machine only.
# every RE:CORD panel
set REC:allowedAddresses "192.168.1.0/24"
Or replace the list outright in the config, keeping loopback in it:
allowedAddresses = {
"127.0.0.0/8", -- loopback
"::1",
-- "100.64.0.0/10", -- Tailscale (CGNAT range)
-- "192.168.1.0/24", -- LAN
-- "172.18.0.0/16", -- Docker bridge
}
Entries are IPv4 CIDR blocks or literal addresses. IPv6 has no CIDR support, so write the address in lowercase canonical form. An entry that can never match is reported on startup.
0.0.0.0/0 publishes the panel to the entire internet, leaving the token as the only protection.trustedProxies
Behind a reverse proxy every request arrives from the proxy itself, so the address check can no longer see the real client. Register the proxy instead:
trustedProxies = {
"127.0.0.1",
}
Requests from a registered proxy skip the address check, which means access control becomes the proxy's job — configure authentication there (Caddy basic_auth, nginx allow / deny, Cloudflare Access). X-Forwarded-For is only written to the debug log, never used to authorise, because any client can forge it.
cloudflared or ngrok on the same host, every visitor arrives as 127.0.0.1. The default loopback entry then no longer means "this machine only" — add authentication at the tunnel.Common setups
| Setup | Config | Notes |
|---|---|---|
| In-game only | Defaults | Nothing to configure |
| SSH port forward | enabled = true only | Source is loopback, already allowed |
| Tailscale / WireGuard | Add 100.64.0.0/10 | Or the single device address |
| Same LAN | Add your LAN range | e.g. 192.168.1.0/24 |
| Reverse proxy + HTTPS | trustedProxies = { "127.0.0.1" } | Authentication must be set on the proxy |
Endpoints
| Method | Path | Scope |
|---|---|---|
GET | /REC_ItemManager/api/session | What your token may read and write |
GET | /REC_ItemManager/api/items | items:read |
PUT | /REC_ItemManager/api/items/<name> | items:write, { cost?, markup?, excludeFromMarket? } |
POST | /REC_ItemManager/api/items/bulk | items:write, { names, operation } |
POST | /REC_ItemManager/api/refresh | items:write, recounts the circulating amounts |
GET | /REC_ItemManager/api/holders | items:read, ?search= ?item= ?limit= |
GET | /REC_ItemManager/api/holders/<citizenId> | items:read |
GET | /REC_ItemManager/api/config | items:read, image source and UI permissions |
GET | /REC_ItemManager/api/market | market:read |
GET | /REC_ItemManager/api/logs | logs:read, ?limit= ?name= |
GET | /REC_ItemManager/api/history | logs:read, ?days= ?name= |
GET | /REC_ItemManager/api/watchlist | watchlist:read |
GET | /REC_ItemManager/api/watch-events | watchlist:read, ?citizenId= |
POST | /REC_ItemManager/api/watchlist | watchlist:write |
POST | /REC_ItemManager/api/watchlist/remove | watchlist:write |
GET | /REC_ItemManager/api/settings | settings:read |
GET | /REC_ItemManager/api/settings/export | settings:read |
POST | /REC_ItemManager/api/settings | settings:write |
POST | /REC_ItemManager/api/settings/reset | settings:write |
POST | /REC_ItemManager/api/settings/reset-all | settings:write |
POST | /REC_ItemManager/api/settings/import | settings:write |
GET | /REC_ItemManager/images/<file> | Item image, read from the inventory resource |
GET | /REC_ItemManager/locale.json | UI strings, address check only |
Everything under /api/* needs the address check and a token carrying the scope. A token that passes the address check but not the scope gets 403 rather than 404: the route exists, your token is just not allowed to use it.
