Build a Discord bot that monitors a Minecraft server
In this tutorial you will build a small discord.js bot that polls one Minecraft server, then creates or updates a single Discord embed with its current state. It only posts again when the state changes, so the channel does not fill with repeated status messages.
The bot uses GET /service-instances/{id}/resources and GET /service-instances/{id}/external-access from the API reference. Conventions shared by every call are in Using the Zaroz API.
What the finished bot does
Reads one service every 60 seconds.
Shows the overall state and primary Minecraft service state.
Includes the public address when the API returns one.
Changes embed colour for running, transitional, stopped, and failed states.
Finds its previous status message after a restart and edits it.
Shows an API error in the same embed without exposing the token.
For a Zaroz Discord Bot service, upload package.json, package-lock.json, and index.js, set the five variables in the service's environment configuration, and use npm start as the start command. Do not upload .env.
5. Verify least privilege
The bot should be able to read GET /service-instances/{id}/resources, but POST /service-instances/{id}/restart should return 403. That is intentional: a public status integration should not be able to restart the server.
If you later add a Discord restart command, create a deliberate permission flow and add services.power for this service. Restrict the Discord command to trusted roles and record who invoked it.
Troubleshooting
The embed says HTTP 401
Replace an expired or revoked token. Confirm you copied the complete token and did not add quotes to the dashboard environment value.
The embed says HTTP 403
Give the token services.read for this exact service. The token owner must also still be able to see the service.
The bot creates a new message after every restart
Grant Read Message History and keep the original status message among the channel's most recent 50 messages.
The address is missing
The API only includes an address on a provision that exposes one. The overall state monitor still works without it.
How often should I poll?
Sixty seconds is a good default. The example enforces a 15-second minimum. For faster event-driven integrations, check the OpenAPI reference for newer platform capabilities before increasing request volume.