
You have a fleet of VPSs — maybe five, maybe fifty — spread across global data centers. As VPS deployments scale, health monitoring becomes increasingly important. Which VPSs operate near their disk capacity? Which are busy, which are idle? Are any VPSs running short of network bandwidth?
Whether you are a DevOps engineer managing environments for a dozen projects, a solo developer with ten edge nodes, or an agency that sets up CMS servers for their clients, you need answers to these questions fast. Yet, you don’t want to deal with the massive overhead of running full-fledged monitoring software on all nodes.
This is where the is*hosting API shines. Detailed information about VPS statuses, configurations, and plans is readily available, among many more actions for VPSs, dedicated servers, storage, services, and VPNs.
In this article, you will learn how to build a command-line tool in Go to query your VPSs' statuses with a single API call, including the current CPU, disk, RAM, network usage, and uptime. Use it as a foundation for VPS health monitoring that you can extend and customize to your specific use case.
As prerequisites, you only need the following:
- Working knowledge of Go (it won't get complicated)
- An is*hosting account
- At least one active VPS
Get Your VPS Ready
If you already have a VPS, feel free to skip this paragraph and move on to creating an API key. Otherwise, start by ordering a VPS.
Don’t worry, setting up one only takes a few easy steps: select the desired VPS plan and follow the order process through.
Once you're in the Client Area, open the API page through the left-hand menu and click on the Add API key button. Enter a name for your key ("default" is a good choice for the first key) and click "Save."
For the purpose of this how-to project, it is sufficient to save the key to an environment variable; for production-level code, using a secrets manager is highly recommended.
On your machine, save the API key to an environment variable called ISHOSTING_API_KEY. The test code we are going to build will refer to that name.
Now your is*hosting VPS is ready for being monitored. In the next section, we'll set up the Go project.
Set up the Go Project
Start the CLI tool project by creating a new, empty directory, cding into it, and running
go mod init <module-path>
where <module-path> can be something like github.com/yourusername/vps-monitor if you plan to share it later, or just vps-monitor if it stays on your machine.
Please ensure to use Go 1.21 or later, as this version adds the min() builtin that the following code uses. No worries, Go's backward compatibility promise makes staying up-to-date with the latest Go toolchain a breeze.
Then create an empty main.go file that we'll fill piecewise with code.
The first code to add includes package imports and a constant:
package main
import (
"encoding/json"
"errors"
"fmt"
"io"
"log"
"net/http"
"net/url"
"os"
"text/tabwriter"
"time"
)
const (
baseURL = "https://api.ishosting.com"
)
Next, we define several types, starting with VPSList. This type represents the response of the API endpoint /vps/list. (More on this endpoint later.) Instead of writing all these types by hand, here is a neat shortcut: Fetch the JSON response from the API documentation of the /vps/list endpoint and use a tool like JSON-to-Go to turn it into a Go type definition.
For generating Go code from more, or all, endpoints of the is*hosting API, you might want to use the full is*hosting API specification with a tool like openapi-codegen or ogen.
This is the generated VPSList type—a slice of structures containing embedded structures:
type VPSList []struct {
ID string `json:"id"`
Name string `json:"name"`
Tags []string `json:"tags"`
Location Location `json:"location"`
Plan Plan `json:"plan"`
Platform Platform `json:"platform"`
Status Status `json:"status"`
CreatedAt int `json:"created_at"`
UpdatedAt int `json:"updated_at"`
}
type Location struct {
Name string `json:"name"`
Code string `json:"code"`
}
type Plan struct {
Name string `json:"name"`
Code string `json:"code"`
}
type Platform struct {
Name string `json:"name"`
Code string `json:"code"`
}
type Status struct {
Name string `json:"name"`
Code string `json:"code"`
Message string `json:"message"`
Uptime int `json:"uptime"`
CPU CPU `json:"cpu"`
RAM RAM `json:"ram"`
Drive Drive `json:"drive"`
Network Network `json:"network"`
State State `json:"state"`
}
type CPU struct {
Value string `json:"value"`
Message any `json:"message"`
}
type RAM struct {
Value string `json:"value"`
Message any `json:"message"`
}
type Drive struct {
Value string `json:"value"`
Message string `json:"message"`
}
type Network struct {
Value string `json:"value"`
Message any `json:"message"`
}
type State struct {
Name string `json:"name"`
Code string `json:"code"`
Message any `json:"message"`
}
For proper error handling, a new error type HTTPStatusError allows capturing the HTTP status as a code and a string, along with the response body that might contain additional error information.
type HTTPStatusError struct {
Code int // e.g., 200
Status string // e.g., "200 OK"
Body []byte
}
func (e HTTPStatusError) Error() string {
return fmt.Sprintf("HTTP %s", e.Status)
}
From here, we can start implementing an API client for the /vps/list endpoint.
Send Authenticated GET Requests
A basic API client is quickly defined:
type IshostingClient struct {
baseURL string
apiKey string
client *http.Client
}
func NewIshostingClient(baseURL, key string) *IshostingClient {
return &IshostingClient{
baseURL: baseURL,
apiKey: key,
client: &http.Client{
Timeout: 30 * time.Second,
},
}
}
The IshostingClient type manages the base URL (in case it ever changes), the API key, and an embedded HTTP client, which is created with a 30-second timeout to prevent requests from blocking indefinitely.
A method Get() receives a query URL consisting of the base URL, the endpoint, and query parameters. It then sets the request header X-Api-Token to authenticate the request with an API key, and two other headers, Accept and User-Agent.
Here’s a gotcha: Some API security services mark Go's default User-Agent as "suspicious," so be sure to set a new, unique user agent.
The Get() method then sends the request, reads the response body, and returns the body or an HTTPStatusError if the HTTP status code indicates an error condition:
func (c *IshostingClient) Get(query string) (body []byte, err error) {
req, err := http.NewRequest("GET", query, nil)
if err != nil {
return nil, fmt.Errorf("cannot create request: %w", err)
}
// set request headers for authentication and accepted format
req.Header.Add("X-Api-Token", c.apiKey)
req.Header.Add("Accept", "application/json")
req.Header.Add("User-Agent", "go-ishosting-api-client/1.0")
// send the request
resp, err := c.client.Do(req)
if err != nil {
return nil, fmt.Errorf("cannot send request: %w", err)
}
defer resp.Body.Close()
// read the response
body, err = io.ReadAll(resp.Body)
if err != nil {
return nil, fmt.Errorf("reading response failed: %w", err)
}
// check for HTTP errors
if resp.StatusCode >= 400 {
return nil, HTTPStatusError{
Code: resp.StatusCode,
Status: resp.Status,
Body: body,
}
}
return body, nil
}
This method already allows sending API requests to an is*hosting endpoint, but it's not intended to be used directly; rather, we'll construct a specific method for calling the /vps/list endpoint.
Fetch VPS List and Status
The /vps/list endpoint lists all VPSs in your portfolio along with valuable monitoring information, including the current CPU, RAM, and drive usage. Only a single API call is required to monitor all your VPSs!
The VpsList() method collects all this information and returns it as a VPSList. For best performance, the /vps/list endpoint breaks up large results into chunks, or “pages”, of data. By setting a limit parameter, you can tell the API how many VPS entries you want per page. To load more pages, simply send another request with an offset parameter that you increase by one for every subsequent page.
Rather than manually building URLs with string concatenation (which can be error-prone), we’ll use the net/url package to construct the query URL in a type-safe way:
func (c *IshostingClient) VpsList(limit, offset int) (list VPSList, err error) {
// construct the query URL, starting with the base URL
query, err := url.Parse(c.baseURL)
if err != nil {
return nil, fmt.Errorf("url.Parse: %w", err)
}
// add the endpoint
query = query.JoinPath("/vps/list")
// add query parameters
params := url.Values{}
params.Add("limit", fmt.Sprint(limit))
if offset > 0 {
params.Add("offset", fmt.Sprint(offset))
}
query.RawQuery = params.Encode()
// pass the query URL to Get()
body, err := c.Get(query.String())
if err != nil {
return nil, fmt.Errorf("GET /vps/list: %w", err)
}
// turn the returned JSON information into a VPSList slice
err = json.Unmarshal(body, &list)
if err != nil {
return nil, fmt.Errorf("unmarshal VPS list: %w", err)
}
With the two methods Get() and VpsList(), the code can retrieve all the information needed for monitoring VPSs; the remaining part is about displaying the results.
Display Data in the Terminal
The /vps/list endpoint returns a JSON object, but the monitoring tool shall print a VPS list to the terminal. Function PrintVPSStats() achieves this with the help of the text/tabwriter package, which can translate text with tabbed columns into a properly aligned table:
func PrintVPSStats(vpsList VPSList) {
w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0)
defer w.Flush()
// Print header
fmt.Fprintln(w, "ID\tName\tLocation\tStatus Name\tStatus Message\tCPU\tRAM\tDrive\tNetwork\tState")
fmt.Fprintln(w, "---\t---\t---\t---\t---\t---\t---\t---\t---\t---")
// Print each VPS information
for _, vps := range vpsList {
fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\n",
vps.ID,
vps.Name,
vps.Location.Name,
vps.Status.Name,
vps.Status.Message,
vps.Status.CPU.Value,
vps.Status.RAM.Value,
vps.Status.Drive.Value,
vps.Status.Network.Value,
vps.Status.State.Name,
)
}
}
Final Wiring and a First Test Run
What's left to do is to:
- fetch the API key from the environment variable set earlier
- create an IshostingClient
- call VpsList()
- and print the response as a table
All of this can be done in main():
func main() {
key := os.Getenv("ISHOSTING_API_KEY")
if len(key) == 0 {
log.Fatal("Please set ISHOSTING_API_KEY")
}
c := NewIshostingClient(baseURL, key)
list, err := c.VpsList(10, 0)
if err != nil {
var httpErr HTTPStatusError
if errors.As(err, &httpErr) {
log.Fatalf("HTTP %s with body %s", httpErr.Status, string(httpErr.Body)[:min(len(httpErr.Body), 100)])
}
log.Fatalf("cannot list VPSs: %v", err)
}
PrintVPSStats(list)
}
The code is now ready to run! You can either copy and paste all of the above code linearly into your local main.go or fetch the full code from the Go Playground. (Note that the code won't work there because of the missing environment variable; also, you might want to refrain from pasting your API key there to avoid leaking it accidentally.)
Run the final code:
go run .
If you have booked, say, one VPS in Germany and one VPS in the Netherlands, your output would look like this:
ID Name Location Status Name Status Message CPU RAM Drive Network State
--- --- --- --- --- --- --- --- --- ---
DE_12356212_3 HSE Group Servers Germany Running Uptime: 0 Days, 18:01:34 0.1% 52% 19 Gb ~34,5 Mb/s Active
NL_12356212_3 HSE Group Servers Nederland Running Uptime: 0 Days, 18:01:34 2.4% 34% 12 Gb ~5,1 Mb/s Active
Conclusion and Next Steps
Congratulations! With a few lines of code, you now have a basic VPS monitoring tool for your terminal. For further practice, consider implementing these enhancements:
- A loop to retrieve information from more than 10 VPSs in batches of 10 VPSs at a time.
- Colored terminal output.
- A loop to update the data every n minutes and refresh the terminal.
This article walked you through building a Go client for the /vps/list endpoint. You're now equipped with the knowledge to call other endpoints and interact with your is*hosting setup programmatically.