# Vultr

- Tool: FlutterFlow
- Difficulty: Intermediate
- Time required: 45 minutes
- Last updated: July 2026

## TL;DR

Connect FlutterFlow to Vultr using a FlutterFlow API Group routed through a Firebase Cloud Function or Supabase Edge Function proxy. The Vultr API v2 uses a clean Bearer token — but that token can create and destroy paid servers, so it must never ship inside the FlutterFlow client binary. Build a mobile server-management panel showing instance status, power state, and reboot controls behind a confirmation dialog.

## Why connect FlutterFlow to Vultr?

Vultr is one of the most developer-friendly cloud VPS providers, known for globally distributed data centers, straightforward per-hour pricing (VPS instances starting around $2.50–$5 per month for entry tiers — verify current rates on vultr.com), and a clean REST API. DevOps teams who run workloads on Vultr often want a quick mobile view of server health: which instances are running, which have gone down, and the ability to reboot a misbehaving server without opening a laptop. A FlutterFlow app querying the Vultr API v2 fills that gap with a native iOS and Android experience.

The central challenge — and the story of this tutorial — is that the Vultr Personal Access Token (PAT) is full-account power. Unlike read-only monitoring tokens, a Vultr PAT can create new servers (generating unexpected charges), delete existing servers (permanent data loss), and modify firewall rules. Placing it in a FlutterFlow API Call header would embed it inside the compiled Flutter app binary, where it can be extracted with standard reverse-engineering tools. The solution is a thin server-side proxy — a Firebase Cloud Function or Supabase Edge Function — that stores the token in environment variables and exposes only the specific safe routes your FlutterFlow app needs: listing instances and rebooting them.

Vultr's API v2 lives at https://api.vultr.com/v2 and uses standard Bearer token authentication (Authorization: Bearer YOUR_PAT). The API is refreshingly RESTful compared to some DevOps tools: GET /instances returns all your servers as an instances[] array, each with fields including id, main_ip, power_status, server_status, label, and region. Reboot is a POST to /instances/{id}/reboot. Vultr optionally lets you restrict your PAT to specific IP addresses — if you enable this, your proxy server's outbound IP must be allowlisted, not the end users' phone IPs.

## Before you start

- A Vultr account at vultr.com with at least one instance running
- A Vultr Personal Access Token generated from Account → API in the Vultr control panel
- A Firebase project (or Supabase project) to host the proxy Cloud Function or Edge Function
- A FlutterFlow project with the API Calls panel accessible
- Basic familiarity with Firebase Console or Supabase Dashboard for deploying a function

## Step-by-step guide

### 1. Create a Vultr Personal Access Token and understand why it must be proxied

Open the Vultr control panel at my.vultr.com and sign in. In the top-right menu click your account name and choose API. Click Enable API if it has not been enabled yet — Vultr requires you to explicitly turn on API access per account. Once enabled, click Add Personal Access Token and give it a descriptive name like 'FlutterFlow Proxy'. Click Create Token and immediately copy the displayed string — Vultr shows the token only once.

Critically important: read the access level your token has before closing this page. By default a Vultr Personal Access Token is full-account-privileged. It can create new instances (generating charges on your credit card), destroy existing instances (permanent data loss with no undo), modify firewall groups, and manage SSH keys. Unlike some cloud providers that offer read-only API scopes, Vultr's PAT model gives you one token per account with full control.

This is why the token must never appear in a FlutterFlow API Call header. Flutter apps compile to a binary distributed to end users' devices. With standard tools, the Authorization header value can be extracted from the compiled app. Once extracted, someone with your Vultr PAT could destroy your servers or create expensive new ones. The proxy pattern in Step 2 eliminates this risk entirely by keeping the token in a server-side environment variable that end users never touch.

Also check whether Vultr's IP Allowlist feature is enabled on your account (visible on the same API settings page). If it is, you will need to add your proxy server's outbound IP to the allowlist. The phone IP addresses of your app users cannot be allowlisted since they change constantly — this is another reason the proxy is architecturally mandatory, not just a best practice.

**Expected result:** You have a Vultr Personal Access Token copied to your password manager and understand that it grants full account access. You understand why it must stay in a server-side environment variable and never be placed in FlutterFlow directly.

### 2. Deploy a Firebase Cloud Function proxy that holds the Vultr token

The proxy is a short Node.js HTTPS Cloud Function that receives requests from FlutterFlow, adds the Vultr PAT from a server-side environment variable, and forwards the request to api.vultr.com. It also enforces a whitelist of allowed Vultr routes so a compromised FlutterFlow app cannot call destructive endpoints.

In the Firebase Console, navigate to your project. Click Functions in the left sidebar. If functions are not yet enabled for this project, click Get Started and follow the prompts to enable Cloud Functions (you may need to upgrade to the Blaze pay-as-you-go plan — Firebase Functions are not available on the Spark free plan, though costs are minimal at low invocation rates).

Create your function. In the Functions dashboard click Create Function or use Firebase CLI. Paste the proxy code shown in the Code field below into your index.js. Set the Vultr PAT using Firebase Functions configuration: run 'firebase functions:config:set vultr.token="YOUR_PAT"' from the Firebase CLI, or store it in Google Cloud Secret Manager if your Firebase project uses the newer functions v2 runtime with defineSecret().

Deploy the function. After deployment, copy the HTTPS trigger URL — it looks like https://us-central1-your-project.cloudfunctions.net/vultrProxy. Test it by opening that URL in a browser (or with curl) and appending /instances — you should see a JSON response with your Vultr instances array. If Vultr's IP allowlist is enabled, add the outbound IP of your Cloud Function region to the allowlist in the Vultr API settings page.

```
// Firebase Cloud Function: vultrProxy/index.js
const functions = require('firebase-functions');
const fetch = require('node-fetch');

// Store token with: firebase functions:config:set vultr.token="YOUR_PAT"
const VULTR_TOKEN = functions.config().vultr.token;
const VULTR_BASE = 'https://api.vultr.com/v2';

// Whitelist: only these path prefixes are allowed
const ALLOWED = [
  '/instances',  // GET list, or GET /instances/{id}
];
// Allowed write actions (POST only on these exact paths)
const WRITE_ALLOWED = [
  '/reboot',
];

exports.vultrProxy = functions.https.onRequest(async (req, res) => {
  // CORS for FlutterFlow web builds
  res.set('Access-Control-Allow-Origin', '*');
  res.set('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
  res.set('Access-Control-Allow-Headers', 'Content-Type, Authorization');
  if (req.method === 'OPTIONS') { res.status(204).send(''); return; }

  const path = req.path || '/';
  const method = req.method;

  // Enforce whitelist
  const readOk = method === 'GET' && ALLOWED.some(p => path.startsWith(p));
  const writeOk = method === 'POST' && WRITE_ALLOWED.some(p => path.endsWith(p));
  if (!readOk && !writeOk) {
    res.status(403).json({ error: 'Route not permitted by proxy whitelist' });
    return;
  }

  const qs = Object.keys(req.query).length
    ? '?' + new URLSearchParams(req.query).toString()
    : '';
  const url = `${VULTR_BASE}${path}${qs}`;

  const fetchOptions = {
    method,
    headers: {
      'Authorization': `Bearer ${VULTR_TOKEN}`,
      'Content-Type': 'application/json'
    }
  };
  if (method === 'POST' && req.body) {
    fetchOptions.body = JSON.stringify(req.body);
  }

  const vultrRes = await fetch(url, fetchOptions);
  // Vultr returns 204 No Content for successful actions
  if (vultrRes.status === 204) {
    res.status(204).send('');
    return;
  }
  const data = await vultrRes.json();
  res.status(vultrRes.status).json(data);
});
```

**Expected result:** The Cloud Function is deployed and accessible at an HTTPS URL. Calling GET {proxy-url}/instances from a browser returns your Vultr instances JSON. POST {proxy-url}/instances/{id}/reboot returns 204. The Vultr PAT is visible only in the Cloud Function environment configuration, not in any FlutterFlow field.

### 3. Create the FlutterFlow API Group pointing at the proxy

In FlutterFlow, click API Calls in the left navigation panel. Click + Add → Create API Group. Name it 'Vultr'. In the Base URL field, paste your deployed Cloud Function HTTPS URL — for example, https://us-central1-your-project.cloudfunctions.net/vultrProxy. Do not put https://api.vultr.com/v2 here. The FlutterFlow app must never call Vultr directly.

In the Headers section, do not add an Authorization header. Your proxy already adds the Vultr Bearer token server-side. If you later add app-level auth to the proxy (so that only authenticated FlutterFlow users can use the proxy), you could add a header here for that purpose — but that is a separate concern from the Vultr token itself.

Click Save Group.

Now add the first API Call: click + Add inside the Vultr group → Create API Call. Name it 'List Instances'. Set method to GET, path to /instances. Click Save. In the Response & Test tab, click Test API Call. The proxy forwards the call to Vultr and returns the response. You should see the instances[] array with your server objects. Click Generate JSON Paths — the key paths are: $.instances[*].id, $.instances[*].label, $.instances[*].main_ip, $.instances[*].power_status, $.instances[*].server_status, $.instances[*].region, $.instances[*].vcpu_count, $.instances[*].ram, $.instances[*].disk.

Add a second API Call: name it 'Reboot Instance'. Method POST, path to /instances/{{ instance_id }}/reboot. In the Variables tab add a String variable named instance_id. No request body is needed for a Vultr reboot call. Click Save.

```
{
  "group_name": "Vultr",
  "base_url": "https://us-central1-your-project.cloudfunctions.net/vultrProxy",
  "headers": [],
  "calls": [
    {
      "name": "List Instances",
      "method": "GET",
      "path": "/instances",
      "json_paths": [
        "$.instances[*].id",
        "$.instances[*].label",
        "$.instances[*].main_ip",
        "$.instances[*].power_status",
        "$.instances[*].server_status",
        "$.instances[*].region"
      ]
    },
    {
      "name": "Reboot Instance",
      "method": "POST",
      "path": "/instances/{{ instance_id }}/reboot",
      "variables": [{ "name": "instance_id", "type": "String" }]
    }
  ]
}
```

**Expected result:** The Vultr API Group is saved in FlutterFlow pointing at the proxy URL. The List Instances test call returns the instances[] array with JSON Paths generated. The Reboot Instance call is configured with an instance_id variable.

### 4. Build the server list with power status coloring

Open the FlutterFlow page where you want the server dashboard. Add a Backend Query to the page using the List Instances API Call — no variables needed since the call is a simple GET. This loads the server list when the page opens.

Add a ListView to the page. Inside it, create a list item Container containing a Row. In the Row, add three sections: a colored status dot (a small CircleAvatar or Container with conditional background), a Column with two Text widgets (server label and IP address), and a Text widget on the right showing the region code.

For the status dot, select it and open Conditional Logic. Set the condition: if $.instances[*].power_status equals 'running', set the background color to a green (#22C55E or similar). Otherwise (stopped, unknown), set background to red (#EF4444). Bind the label Text to $.instances[*].label and the IP Text to $.instances[*].main_ip. Bind the region Text to $.instances[*].region.

Add a Pull To Refresh action on the ListView so users can swipe down to refresh. In the ListView's Actions, add an action of type Refresh — target the Backend Query on the page to re-run.

For the reboot action: tap the list item Container and add an On Tap action → Navigate to a new page (server detail page). Pass the instance_id and label as page parameters to the detail page. On the server detail page, show the full server info (vcpu_count, ram, disk, server_status) fetched from a second /instances/{id} call. Add a 'Reboot Server' button at the bottom. Wire the button's On Tap action to first show an Alert Dialog widget (name: 'Confirm Reboot', message: 'This will interrupt all active connections to {server label}. Continue?', confirm button, cancel button). Only after confirm is pressed, call the Reboot Instance API Call with the instance_id parameter.

After the reboot call returns, show a Snack Bar widget with a success message. The server may briefly show a non-running status during the reboot — add a 'Refreshing…' state or simply tell the user to pull-to-refresh after a minute.

If this proxy and widget setup feels like a lot to wire together, RapidDev's team builds FlutterFlow integrations like this regularly — free scoping call at rapidevelopers.com/contact.

**Expected result:** The page loads a ListView of Vultr server cards, each showing a green or red status dot, the server label, IP address, and region. Pull-to-refresh re-fetches the list. Tapping a server navigates to a detail page. Tapping Reboot shows a confirmation dialog before calling the proxy.

### 5. Add pull-to-refresh, error handling, and per-server details

The basic list from Step 4 is functional but needs error handling and detail views to be production-ready. Open the server list page in FlutterFlow and select the ListView. In the Actions panel, confirm the Pull to Refresh action triggers the List Instances Backend Query. Verify the loading state is handled — FlutterFlow's Backend Query component automatically shows a loading spinner while the API call is in progress, but you should set what appears in the list area during the initial load (a CircularProgressIndicator or skeleton cards).

For error handling: on the List Instances Backend Query, click the Error State button and set a fallback widget to display. A simple Column with a warning icon and the text 'Could not load servers. Check your connection and pull to refresh.' is sufficient. This covers cases where the Cloud Function is temporarily unavailable or the Vultr API returns an error.

For the server detail page: add a page parameter (String: instanceId) and a separate Backend Query for GET /instances/{instance_id} to load detailed data for that server. The Vultr API returns single-instance detail at GET /instances/{id} with additional fields: vcpu_count, ram (in MB), disk (in GB), os (OS type string), date_created, and tags[]. Display these in a detail Column layout. Below the specs section, add the Reboot button with the confirm dialog from Step 4.

For the reboot success case: after the Reboot Instance API Call returns successfully (the proxy forwards Vultr's 204 No Content), show a Snack Bar notification and add a brief wait before re-running the detail Backend Query. The server status will momentarily show as non-running during reboot — set the status chip to 'Rebooting…' in an App State variable between the confirm tap and the next status refresh.

**Expected result:** The server list shows an error state when the proxy is unreachable. The detail page shows full server specs. The Reboot flow shows a confirm dialog, triggers the proxy call, shows a success snackbar, and re-fetches server status after a brief delay.

## Best practices

- Never place the Vultr Personal Access Token in a FlutterFlow API Call header — it can create and destroy paid infrastructure and must live only in a server-side Cloud Function environment variable.
- Enforce a strict whitelist in the proxy: permit only the specific route prefixes and HTTP methods your app needs (GET /instances, POST /instances/{id}/reboot) and block everything else with a 403.
- If Vultr's IP Allowlist is enabled, add the proxy server's outbound static IP — not the end users' phone IPs, which change constantly and cannot be allowlisted.
- Always require a confirmation dialog before any reboot or power action — a fat-finger tap on a reboot button with no confirmation can interrupt active user sessions on the server.
- Handle the post-reboot status lag explicitly: show a 'Rebooting…' state for 60 seconds after a reboot call rather than immediately re-fetching status, since the server will correctly report as not-running during the reboot cycle.
- Use the per_page=500 query parameter on the List Instances call to avoid pagination surprises when an account has more than the default page size of instances.
- Add CORS headers to the proxy even if you currently only need native iOS/Android builds — if you ever add a FlutterFlow web build or test in the browser preview, missing CORS headers will cause XMLHttpRequest errors.
- Tag Vultr instances in the Vultr control panel (e.g., 'staging', 'production') and filter by tag in the proxy or in FlutterFlow's list to show only the relevant servers for the app's audience.

## Use cases

### Mobile server control panel for a solo developer or small ops team

A FlutterFlow app that shows all Vultr instances with their label, IP address, power status (running / stopped), and server health. A reboot button on each card triggers a POST to the proxy, which forwards it to Vultr's reboot endpoint. The app is used only internally by the developer or a small team, so access is gated behind FlutterFlow's built-in authentication before the app can make any proxy calls.

Prompt example:

```
A home screen with a ListView of server cards. Each card shows the server label, main IP, region, power status (green dot for running, red for stopped), and a 'Reboot' button. Tapping Reboot opens a confirmation alert dialog. Confirming sends a reboot request through the Cloud Function proxy. A success snackbar appears when the proxy returns 204.
```

### Infrastructure at-a-glance widget in an existing ops app

Add a Vultr tab to an existing FlutterFlow operations or on-call app alongside other data sources like StatusCake uptime tests or Datadog alerts. The Vultr tab shows a compact list of servers sorted by power status, highlighting any that are stopped or in error state. Because the ops app is already authenticated and team-shared, the Cloud Function proxy is already required, and CORS headers on the proxy support the FlutterFlow web build as well.

Prompt example:

```
A 'Servers' tab in an existing ops FlutterFlow app. Show all Vultr instances sorted by power_status with stopped instances first. Display label, main_ip, region, and a colored status badge. Show the total count of running vs stopped instances in a header row. Tapping a server opens a detail sheet with server_status, vcpu_count, ram, and disk fields.
```

### Staging environment manager for a development team

A FlutterFlow app used by developers to start and stop staging Vultr instances on demand — avoiding paying for running servers overnight when no one is using them. The proxy exposes start, stop, and reboot endpoints in addition to list. Each action requires a confirmation dialog. The app shows last-seen power state and estimated hourly cost for each tagged staging instance. Only instances tagged 'staging' are shown, enforced in the proxy layer.

Prompt example:

```
A staging manager app screen. List only Vultr instances with the label containing 'staging'. For each, show power status, current monthly cost estimate, and three action buttons: Start, Stop, Reboot — each requiring a confirmation popup. Show a warning badge on any staging server running for more than 12 hours.
```

## Troubleshooting

### XMLHttpRequest error or CORS policy blocked in the FlutterFlow web preview

Cause: The Cloud Function proxy is not returning CORS headers, or the CORS origin is set too restrictively. The browser (FlutterFlow web preview) enforces CORS; native mobile builds do not.

Solution: In the proxy Cloud Function, confirm the Access-Control-Allow-Origin header is set to '*' (or to the specific FlutterFlow preview domain) before any other logic. Also handle the OPTIONS preflight request method by returning 204 immediately. Redeploy the function after making changes. CORS errors do not appear when testing on a real mobile device — only in web/browser contexts.

### All Vultr API calls return 403 Forbidden from the proxy

Cause: Either the Vultr PAT stored in the Cloud Function configuration is incorrect or expired, or Vultr's IP Allowlist feature is enabled and the Cloud Function's outbound IP is not in the allowlist.

Solution: First, verify the token in Firebase Functions configuration by running 'firebase functions:config:get' and confirming vultr.token is set and non-empty. Test the token by calling the Vultr API directly from a browser or curl: 'curl -H "Authorization: Bearer YOUR_PAT" https://api.vultr.com/v2/instances'. If that works but the proxy returns 403, check Vultr's API settings page for the IP Allowlist setting. If enabled, add the outbound IP of your Firebase Cloud Function region (findable via Google Cloud Console → Cloud Functions → your function → networking details) to the Vultr API allowlist.

### The List Instances call succeeds but instances[] is empty even though the Vultr account has running servers

Cause: The Vultr API paginates results and may return an empty page if query parameters are misconfigured, or the API token used in the proxy belongs to a different Vultr account than the one with servers.

Solution: Add the query parameter per_page=500 to the List Instances path (/instances?per_page=500) to request up to 500 instances in one call. Also verify the token in the proxy is from the correct Vultr account by checking the account email shown in the Vultr control panel matches the account that owns the instances. If you have multiple Vultr accounts, tokens are not interchangeable between them.

### Reboot button triggers the API Call but the server status does not update to 'running' on the next refresh

Cause: Vultr reboots take 30–90 seconds. If the app re-fetches instance status too quickly after the reboot call, the server will still show as not-running. This is expected behavior, not an error.

Solution: After the reboot API Call returns 204, set an App State variable (e.g., isRebooting) to true and show a 'Rebooting…' label on the status chip. Use a FlutterFlow Timer action to wait 60 seconds before re-running the detail Backend Query to refresh the server status. Reset the isRebooting App State variable when the refreshed status returns 'running'.

## Frequently asked questions

### Can I put the Vultr API token directly in the FlutterFlow API Group header to skip setting up a Cloud Function?

Technically FlutterFlow allows it, but you should not for Vultr specifically. The Vultr Personal Access Token grants full account control — it can create new billable servers and delete existing ones. Flutter app binaries distributed to users can be reverse-engineered and headers extracted. For a quick local prototype on a device only you use, you can accept this risk. For anything else, the Cloud Function proxy is mandatory and takes about 20 minutes to set up.

### Does the proxy pattern add significant latency to API calls?

For a management dashboard checking server status, the additional 50–150ms round-trip through a Firebase Cloud Function in the same region is imperceptible to users. The security guarantee — the Vultr PAT never leaving your server — is worth this trade-off. Deploy the Cloud Function in the Firebase region geographically closest to your Vultr server region for minimum added latency.

### Can I add a 'create new instance' button to the FlutterFlow app?

Yes, but approach it carefully. Creating a Vultr instance via POST /instances with a FlutterFlow action requires adding that route to the proxy whitelist. More importantly, creating instances incurs charges on your Vultr account — add the proxy for this endpoint only if the FlutterFlow app users are authorized to spend money on your account. Require both an authentication gate in the proxy (check a secret header the FlutterFlow app passes) and a confirmation dialog in the FlutterFlow UI before sending the create request.

### How do I handle Vultr API rate limits?

Vultr documents per-account API rate limits on their API documentation page — check current limits at vultr.com/api. For a mobile dashboard with pull-to-refresh driven by user gestures, you are unlikely to hit rate limits under normal use. If you add automatic polling or a timer that refreshes the list every few seconds, monitor your rate limit headroom. Add a debounce to the pull-to-refresh action (minimum 10 seconds between refreshes) to prevent accidental rapid-fire refreshes.

---

Source: https://www.rapidevelopers.com/flutterflow-integrations/vultr
© RapidDev — https://www.rapidevelopers.com/flutterflow-integrations/vultr
