FluxBilling
APIs & Integrations

Customer REST API

How a customer creates an API key in the client portal and uses it to manage their own services, orders and billing documents from a script.

Updated · 2026-09-03

What the customer REST API is for

The customer REST API lets one of your customers drive their own services from a script, a monitoring tool or their own software, instead of clicking through the client portal. Everything the API can do, the customer can already do by hand in the portal: list services, reboot a VPS, reinstall an operating system, buy an add-on, place an order, read invoices and pay them from the credit balance.

The API is authenticated with an API key that the customer creates themselves. A key never grants more than the person who created it already has — it is limited by the scopes chosen at creation and re-checked against that person's live account permissions on every request. Staff logins cannot be used with the API: it is a customer-only surface.

Note: The API is labelled Beta in the client portal. It is stable enough to build on, but endpoints and response shapes may still change before the stable release.

Switching the API on or off

The whole surface is governed by a company-wide feature switch. In the admin panel open Settings and find Feature Toggles; the entry is called Customer API (“REST API keys customers create in the client panel to manage their services”). With it switched off, the API Keys page disappears from the client portal and every API request is refused with a feature disabled error. It is on by default.

Creating a key in the client portal

Your customer does this themselves. In the client portal:

  1. Open Profile (the account menu, top right) or Settings in the sidebar — both open the same page.
  2. In the left-hand list, under the Security heading, choose API Keys. The item carries a Beta badge and only appears when the Customer API feature is enabled.
  3. Select New key. The form has three numbered steps.
  4. Name the key. Type a Label (for example Terraform or monitoring script) and, if you want the key to stop working on a given day, an Expires on date. The label is what identifies the key in the list and in the request log.
  5. Choose what it may do. Tick the scopes. read is always on and cannot be unticked; the other four are optional. Ticking destructive shows a warning suggesting an IP restriction and an expiry.
  6. Restrict where it can be used from. Optionally paste one or more addresses or CIDR ranges, separated by commas or newlines (for example 203.0.113.10, 2001:db8::/32). Leave it empty to allow any source. Up to 50 entries are accepted, and an entry that is not a valid address or range is refused with a message naming it.
  7. Select Create key.

The new key then appears once, in a green panel headed Your new API key, with a Copy button. It is shown only this once and cannot be recovered — if it is lost, the key has to be revoked and a new one created. Selecting I have saved it hides it, and leaving the page clears it too.

The key format

A key looks like fbk_ followed by an eight-character prefix, an underscore and a 96-character secret, for example fbk_a1b2c3d4_9f8e…. Only the prefix is kept in readable form — the key list shows fbk_a1b2c3d4_… so a customer can tell two keys apart. The secret itself is stored only as a one-way hash, which is why nobody, including your staff, can read it back.

Scopes

The five scopes a key can carry
ScopeWhat it does
readAlways included. Lists and reads services, VPS, dedicated servers, add-ons, upgrades, invoices, orders, balance and usage.
servicesPower actions, reinstall, password reset, snapshots, backups, provider actions and SSH keys on services the customer owns; also withdrawing a pending cancellation.
ordersPlaces new orders, buys add-ons and requests upgrades. Each of these raises an invoice or a proforma.
billingPays invoices and proformas from the credit balance, and asks for a renewal document early.
destructiveCancels services (at the end of the term or immediately) and cancels add-ons.

Scopes are fixed for the life of a key. To change what a key may do, revoke it and create a new one.

Keys inside a shared account

When the customer is part of a shared account, a key belongs to the individual who created it, and it can never out-rank that person's role. A team member may only choose a scope their permissions already cover:

  • read needs at least one of View Services, View Invoices or View Billing.
  • services needs Manage Services or Power Services.
  • orders needs Manage Services.
  • billing needs Pay Invoices.
  • destructive needs Cancel Services.

Asking for a scope the role does not cover is refused at creation with a message naming the permission required. Permissions are also re-read on every request, so removing a permission takes effect immediately on existing keys. Credit balances are per person, not per account: paying from credit always spends the balance of whoever the key belongs to. See Team Members for how those permissions are granted.

Managing keys afterwards

The API Keys page lists every key the customer has ever created, newest first. Each row shows the label, the visible prefix, the scope chips, when the key was last used and from which address, the expiry date if any, and the IP allowlist if any. Revoked keys stay in the list, greyed out with a revoked badge; a key past its expiry date carries an expired badge.

Three controls sit at the end of each row:

  • Recent requests — opens a log of the most recent calls made with that key: method, endpoint, status code, source address and time. Failed calls appear here too, including ones refused because the key was revoked, expired, out of allowlist or over its rate limit, which makes this the place to diagnose a lock-out. Entries are kept for 30 days.
  • Edit — changes the label, the IP allowlist and the expiry date. Scopes are not editable and the form says so.
  • Revoke — asks for confirmation, then rejects every request with that key from that moment. It cannot be undone.

A bar above the list shows how many active keys the customer holds out of the maximum (10 unless your company has been given a different limit), the hourly request limit, and the Base URL with a copy button. When the maximum is reached the New key button is disabled; an unused key has to be revoked first.

Keys can only be created and edited from the portal — the API cannot mint or change keys. Your staff see the same list from the API Keys tab of the customer's record in Client Details: label, prefix, scopes, last use, and the same request log. Viewing it needs the permission to view clients, and revoking a leaked key needs the permission to edit them. Staff can never read or create the secret.

Base path and versioning

Every call goes to https://your-panel-domain/api/v1 — the exact value is shown as Base URL on the API Keys page. The version is the /v1 path segment; there is no version header and no other form of negotiation. All paths below are relative to that base.

Three documentation endpoints need no key at all and can be opened in a browser:

  • GET /docs — the rendered reference (the Usage guide link on the API Keys page).
  • GET /guide.md — the same guide as Markdown, useful as input for a code generator or an assistant.
  • GET /openapi.json — the machine-readable description of every route.

Authenticating a request

Send the key as a bearer token on every call:

Authorization: Bearer fbk_a1b2c3d4_…

There is no other authentication method, no query-string key and no session. A missing or malformed header is answered with 401. Keep the key out of URLs and logs; if it leaks, revoke it from the portal.

Beyond the key itself, three further checks run on every call, in this order: the key must hold the scope the route needs; the person must hold the account permission the route needs; and, for anything that changes something, the customer account must not be suspended. A suspended customer keeps full read access and is refused every write with 403 ACCOUNT_SUSPENDED.

Rate limits

Two independent limits apply.

The hourly window

Each key may make 1,000 requests per hour by default. The figure is set for the whole company, is the same for every key, and is shown on the API Keys page. Every request counts, including refused ones. Three headers come back on every response:

Rate-limit headers
HeaderWhat it does
X-RateLimit-LimitRequests allowed in the window.
X-RateLimit-RemainingHow many are left.
X-RateLimit-ResetWhen the window restarts, as a Unix timestamp in seconds.

Going over answers 429 with the code RATE_LIMITED and a Retry-After header.

Per-action cool-downs

Expensive or disruptive actions carry their own cool-down on top of the hourly window. They are counted per person and per resource, so holding two keys does not open two windows.

Cool-down per action
ActionWhat it does
Power action on a service or VPS10 per minute per service.
Power action on a dedicated server5 per minute across all of the customer's servers, counting failed attempts too.
Reinstall1 per 5 minutes per service.
Password reset1 per 10 minutes per service.
Renewal request1 per 10 minutes per service.
Backup create or restore2 per 10 minutes per service.
Cancellation5 per minute per service.
Order creation, add-on purchase, upgrade request30 per 15 minutes.
Paying an invoice or proforma from credit10 per minute per document.

Only accepted calls consume a cool-down, so a refused attempt — a wrong image, a machine that is still building, a validation error — never locks the customer out of the corrected retry. The refusal is 429 with the code ACTION_RATE_LIMITED, a Retry-After header and a retry_after_seconds field in the body.

The response envelope

Every response is JSON. A success looks like this:

{ "success": true, "data": … }

Some endpoints add a message or a pagination object beside data. Every refusal, without exception, looks like this:

{ "success": false, "error": "<readable message>", "code": "<MACHINE_CODE>", "details": [ { "field": "…", "message": "…" } ] }

details is present only on validation failures. Branch on code, never on the wording of error.

Status codes and what they mean
Status and codeWhat it does
400 BAD_REQUESTInvalid input, or the action does not apply to this service. details lists the field errors. An id that is not a valid identifier is a 400, not a 404.
401 UNAUTHORIZEDMissing, malformed, unknown, expired or revoked key.
403 SCOPE_REQUIREDThe key does not hold the scope this route needs; the body names it in a scope field.
403 FORBIDDENAn account permission, the IP allowlist, the account type or the service state refused the call.
403 ACCOUNT_SUSPENDEDThe customer is suspended and may only read.
403 ACTION_NOT_ALLOWEDThe provider behind this service does not expose that action to customers.
403 FEATURE_DISABLEDThe Customer API is switched off, or the module the route belongs to is (dedicated-server routes need the Inventory module; VPS snapshot routes need the VPS module).
404 NOT_FOUNDNo such resource, or it is not owned by this customer. The two are deliberately not distinguished.
409 CONFLICTThe resource is in the wrong state — a machine still being built, or a billing document already open on the service.
424 PROVIDER_ERRORA dedicated server's management controller did not complete the power action.
429 RATE_LIMITED / ACTION_RATE_LIMITEDThe hourly window or a per-action cool-down.
500 / 502 / 503A platform-side failure. The message is deliberately generic; nothing internal is exposed.

Warning: A 5xx or a 429 may safely be retried — except on anything that moves money (paying a document, creating an order, buying an add-on, requesting an upgrade). A 5xx there may already have applied. Re-read the order, document or balance first, and retry only if nothing happened.

Pagination

List endpoints accept ?page= (starting at 1) and ?limit=. A limit above 200 is silently reduced to 200 rather than refused. The default is 20 on services, invoices, proformas, orders and credit transactions, and 50 on the VPS and dedicated-server lists. Several lists also accept ?status=.

Responses carry a pagination object with page, limit, total and a page count. On the VPS and dedicated-server lists it sits beside data; on the panel-shaped lists (services, invoices, proformas, orders, transactions) it sits inside data. The services and invoices lists add a stats object with counts and money totals by status.

Every operation

The scope column is the scope the key must hold. Read operations additionally need the matching view permission when the customer is part of a shared account.

Index and documentation

Index and documentation
FieldWhat it does
GET /readWho the key belongs to, its label and scopes, the current rate-limit counters and a set of links. The right first call.
GET /docs, GET /guide.md, GET /openapi.jsonNo key needed.

Account

Account
FieldWhat it does
GET /accountreadProfile, company, country, currency, credit balance and, for a team member, the permissions in force.
GET /account/balancereadCredit balance and currency.
GET /account/usagereadService counts by status, the recurring monthly amount, unpaid invoice count and total, amount paid this month, hourly charges this month and the balance.
GET /account/transactionsreadThe credit ledger, paginated.

Services

Services of every kind
FieldWhat it does
GET /servicesreadEvery service the customer owns, of every kind.
GET /services/{id}readOne service with its type-specific detail.
GET /services/{id}/statsreadUsage statistics.
GET /services/{id}/actionsreadWhich actions this service accepts, and which backend it is on. Read this before offering a button.
GET /services/{id}/backupsreadProvider backups.
GET /services/{id}/gamereadGame-server detail, on game services only.

Service control

Actions on a service
FieldWhat it does
POST /services/{id}/actions/{action}servicesThe generic dispatcher: start, stop, restart, status, stats, plus any action the provider behind the service exposes to customers. Parameters go in a params object.
POST /services/{id}/power/{action}servicesA power action by name, whatever the backend.
POST /services/{id}/reinstallservicesReinstalls the operating system. Erases the disk.
POST /services/{id}/passwordservicesSets a new root password. Body { "password": "…" }, at least 8 characters.
POST /services/{id}/backupsservicesCreates a provider backup.
POST /services/{id}/backups/{backupId}/restoreservicesRestores one.
POST /services/{id}/game/power/{action}servicesGame-server power: start, stop, restart, kill.
POST /services/{id}/canceldestructiveRequests cancellation. Body { "reason": "…", "immediate": false }; immediate: true terminates now and destroys the disks.
DELETE /services/{id}/cancelservicesWithdraws a scheduled cancellation.
POST /services/{id}/renewbillingIssues the next renewal document early. Refused while any document is already open on the service.

Note: Power and reinstall calls are asynchronous. A job identifier in the response means the request was accepted, not that it finished. Poll the resource rather than repeating the call.

VPS

VPS
FieldWhat it does
GET /vps, GET /vps/{id}readThe VPS collection, with specifications, addresses, operating system and machine state folded in.
GET /vps/{id}/ipsreadThe authoritative address list for that machine.
GET /vps/{id}/statsreadProcessor, memory, disk and network figures.
GET /vps/{id}/os-imagesreadExactly the images a reinstall of this machine will accept. An empty list means none is enabled for its plan.
POST /vps/{id}/start, /stop, /shutdown, /reboot, /restartservicesPower. stop sends a graceful shutdown and forces power-off after the grace window; restart is the same as reboot.
POST /vps/{id}/power/{action}servicesThe same set by name: start, stop, restart, reboot, shutdown, poweroff.
POST /vps/{id}/reinstallservicesBody { "image_id": "…", "password": "…" }. Only an image from the list above is accepted. Erases the disk.
POST /vps/{id}/passwordservicesNew root password, at least 8 characters. The machine must be running.
GET / POST /vps/{id}/snapshots, POST /vps/{id}/snapshots/{snapshotId}/revert, DELETE /vps/{id}/snapshots/{snapshotId}read / servicesSnapshots, only where you have enabled customer snapshots for that plan.
DELETE /vps/{id}destructiveCancels the VPS. With immediate: true the disks are destroyed at once.

Every VPS carries two different states: status is the billing state (pending, active, suspended, terminated), and instance.state is the machine state (creating, running, stopped, paused, error). A machine that has not been built yet, or whose build failed, has no instance and answers 404 to power, reinstall and password calls even though the service itself is perfectly valid. The detail response also carries a capabilities object saying which controls you have allowed for that plan; a control that is off is refused before the hypervisor is even asked. See VPS Instances.

Dedicated servers

These routes appear only when the Inventory module is enabled.

Dedicated servers
FieldWhat it does
GET /dedicated, GET /dedicated/{id}readThe list, and one server with hardware, location, provisioning and operating-system state.
GET /dedicated/{id}/ipsreadSubnets and addresses.
GET /dedicated/{id}/power-statusreadCurrent power state.
GET /dedicated/{id}/statsreadHardware specification and live power state.
GET /dedicated/{id}/trafficreadBandwidth usage.
GET /dedicated/{id}/provision-statusreadDeployment or reinstall progress.
GET /dedicated/{id}/os-imagesreadImages this server may be reinstalled onto.
POST /dedicated/{id}/powerservicesBody { "action": "on" | "off" | "restart" }. Also available as POST /dedicated/{id}/power/{action}.
POST /dedicated/{id}/reinstallservicesBody { "osImageId": "…" }. Erases all data.
POST /dedicated/{id}/passwordservicesNew root password. On servers the platform provisions itself this rebuilds the machine and returns a generated password.

Add-ons and upgrades

Add-ons and upgrades
FieldWhat it does
GET /services/{id}/optionsreadAdd-ons currently active on the service. Cancelled ones disappear from this list.
GET /services/{id}/options/availablereadAdd-ons the product offers, with prices; those already held are marked.
POST /services/{id}/optionsordersBuys one. Body { "optionId", "billingType": "one_time" | "recurring", "billingCycle", "quantity" }. Charges a full cycle and starts the add-on's own cycle today.
DELETE /services/{id}/options/{optionId}destructiveCancels an add-on immediately. {optionId} is the catalogue option identifier, not the row identifier.
GET /services/{id}/upgradesreadAvailable upgrade paths with a prorated preview of each, plus any upgrade already pending.
POST /services/{id}/upgradesordersRequests a plan change. Body { "toProductId": "…" }.
POST /services/{id}/upgrades/optionsordersAdds and removes add-ons in one prorated change, aligned to the service's next due date. Body { "addOptions": [], "removeOptions": [] }; the two lists must not overlap.
GET /upgrades/{upgradeId}readOne upgrade, its billing document and the order it created.
POST /upgrades/{upgradeId}/cancelordersCancels an upgrade that is still waiting for payment.

An upgrade executes only once its document is paid, moving through awaiting_payment, executing and completed. A change that nets to zero or to a credit has no document, completes at once, and can no longer be cancelled. Credit is never applied to an upgrade automatically.

Products and orders

Products and orders
FieldWhat it does
GET /productsreadThe orderable catalogue with cycles and prices.
GET /products/{id}readBilling cycles, add-on options, and the operating-system images an order will actually accept.
POST /ordersordersPlaces an order. Either { "productId", "billingCycle", … } or a multi-item items array of up to 50 entries, each of which may carry its own options.
GET /orders, GET /orders/{id}readOrders, their status and the services they produced.

Invoices and proformas

Billing documents
FieldWhat it does
GET /invoices, GET /invoices/{id}readInvoices; the detail carries line items and the amount already settled. ?status=unpaid narrows the list.
POST /invoices/{id}/paybillingPays from the credit balance. Body {} for the full amount, or { "amount": 12.5 } for part of it.
GET /proformas, GET /proformas/{id}readThe same for proformas.
POST /proformas/{id}/paybillingPays a proforma from credit. A paid proforma converts into an invoice, which is returned in the response.

Credit is the only payment method on this surface. Card and gateway payments stay in the client portal, because they need a browser. See Invoices & Payments.

SSH keys

SSH keys
FieldWhat it does
GET /ssh-keysreadThe customer's stored public keys.
POST /ssh-keysservicesBody { "name": "laptop", "publicKey": "ssh-ed25519 AAAA…" }. The response carries the stored key and its fingerprint.
DELETE /ssh-keys/{id}servicesRemoves one.

Worked example 1 — list the VPS and reboot one

Request:

curl -H "Authorization: Bearer fbk_a1b2c3d4_…" https://your-panel-domain/api/v1/vps

Response:

{ "success": true, "data": [ { "id": "36d05681-…", "name": "Smoke VPS", "hostname": "vps1.example.com", "status": "active", "kind": "vps", "provider": "native", "product": { "id": "…", "name": "VPS 2G" }, "billing": { "cycle": "monthly", "amount": "5.0000", "currency": "EUR", "next_due_date": "2026-09-21T21:00:00.000Z" }, "cancellation": null, "location": "Bucharest", "primary_ip": "203.0.113.10", "ip_addresses": [ { "address": "203.0.113.10", "version": 4, "prefix_length": 24, "purpose": "public_ip" } ], "instance": { "state": "running", "vcpus": 2, "memory_mb": 2048, "disk_gb": 40, "os": "Debian 12", "rescue_mode": false }, "created_at": "…" } ], "pagination": { "page": 1, "limit": 50, "total": 1, "pages": 1 } }

Then, using the identifier from that response:

curl -X POST -H "Authorization: Bearer fbk_a1b2c3d4_…" https://your-panel-domain/api/v1/vps/36d05681-…/reboot

Response:

{ "success": true, "data": { "completed": true, "job_id": "…", "state": "running", "status": "completed" } }

On a busier machine the same call answers { "success": true, "data": { "completed": false, "pending": true, "job_id": "…" } } — the reboot was accepted and is running. Poll GET /vps/{id} for the result. A machine that is still being built answers 409.

Worked example 2 — order a service and pay it from credit

Request:

curl -X POST -H "Authorization: Bearer fbk_a1b2c3d4_…" -H "Content-Type: application/json" -d '{"productId":"…","billingCycle":"monthly","hostname":"srv1.example.com","configOptions":{"os_image_id":"…"}}' https://your-panel-domain/api/v1/orders

Response (201):

{ "success": true, "data": { "order": { "id": "…", "order_number": "ORD-…", "status": "pending", "payment_status": "unpaid" }, "services": [ { "id": "…", "status": "pending_payment" } ], "proforma": { … }, "invoice": null, "document": { "type": "proforma", "id": "…", "status": "unpaid", "total": 12.5, "settled": false }, "settled": false, "next_step": "Pay it: POST /proformas/{id}/pay", "credit_balance": 25.5 } }

Branch on settled, never on the nested rows — when credit settles the order inside the same request, settled is true while the nested order and service objects may still read as unpaid because they were assembled before the payment. Here it is false, so pay the document:

curl -X POST -H "Authorization: Bearer fbk_a1b2c3d4_…" -H "Content-Type: application/json" -d '{}' https://your-panel-domain/api/v1/proformas/…/pay

Response:

{ "success": true, "message": "Proforma paid successfully with credit", "data": { "creditApplied": 12.5, "newBalance": 13.0, "remainingAmount": 0, "proformaPaid": true, "invoice": { "invoice_number": "INV-…", "status": "paid" } } }

Provisioning starts once the document is paid. Watch GET /orders/{id}: the services move from pending to installing to active, or to provisioning_failed. Hourly products are different — they need a minimum credit balance and are charged from credit without a document.

What can go wrong

  • Every call answers 401. The header is not exactly Authorization: Bearer <key>, or the key has been revoked or has passed its expiry date. The key list shows both badges.
  • Reads work, writes answer 403 with a scope name. The key was created without that scope. Scopes cannot be widened — create a new key.
  • 403 from one source only. The key has an IP allowlist and the caller is not on it. The Recent requests log shows the address the platform actually saw, which is the address to add.
  • 403 on a member's key that used to work. Their account permissions changed. Permissions are read live on every request.
  • 404 on a power call, but the service reads fine. The machine behind the service does not exist yet, or its build failed. Treat 404 as an invalid identifier only when reading the service itself fails.
  • 409 on a renewal. A billing document is already open on that service. Pay or cancel it first.
  • The customer cannot find the page. The Customer API feature is switched off for your company, or they are looking at the wrong section — it is under Security, not under Preferences.

Related articles