Why Use the Wrapper?
Section titled “Why Use the Wrapper?”- Automatic CSRF handling — No need to manage tokens manually
- Built-in spinner — Shows a loading indicator during requests
- Less coupling — If the API changes, the wrapper absorbs more of that churn
- Cleaner code — Less boilerplate for you
Importing the Wrapper
Section titled “Importing the Wrapper”The bundled admin and Huraga themes already load the wrapper. For a custom theme, add this to your base layout before your theme JavaScript:
{{ 'js/fossbilling.js'|public_asset_url|script_tag }}{{ 'js/api.js'|public_asset_url|script_tag }}<script src="{{ 'build/js/mytheme.js'|asset_url }}"></script>The script_tag filter adds a cache-busting hash and prevents the same script from being rendered more than once.
If your custom theme does not reuse the bundled theme JavaScript, initialize automatic API forms and links after the DOM loads:
document.addEventListener('DOMContentLoaded', function() { API._apiForm(); API._apiLink();});CSRF Tokens
Section titled “CSRF Tokens”FOSSBilling uses session-bound CSRF tokens for protected browser API calls. Browser-rendered Twig pages expose CSRFToken and set a fossbilling_csrf cookie. The submitted token must match the server-side session token.
| Call type | CSRF requirement |
|---|---|
| Guest client login/signup and staff login | Required; POST only, same-origin browser context. |
| Guest cart mutations | Required; same-origin browser context. |
Other guest API calls | Not required. |
client or admin API calls with the browser session | Required. |
External client or admin API calls using an API key | Not required. |
The JavaScript wrapper reads the fossbilling_csrf cookie and sends it automatically on same-origin requests, including protected guest calls. Load a FOSSBilling page first to establish the session and token. If you include a CSRFToken form field explicitly, use the current page's token. Guest authentication requires the token in the body or header, not the query string. See the API reference for the protected endpoints.
Making Requests
Section titled “Making Requests”The wrapper follows this pattern:
API.{role}.{method}(endpoint, params, onSuccess, onError, showSpinner)| Parameter | Description |
|---|---|
role | admin, client, or guest |
method | Use get or post for core routes; guest authentication requires post. Prefer post for mutations. |
endpoint | The API endpoint (e.g., "currency/get_pairs") |
params | Request parameters (object) |
onSuccess | Callback function for successful responses |
onError | Callback function for errors. Receives { message, code }. |
showSpinner | Boolean to show/hide the loading spinner (optional, default: true) |
Examples
Section titled “Examples”Get Currency Pairs (Guest)
Section titled “Get Currency Pairs (Guest)”API.guest.get( "currency/get_pairs", {}, function(response) { console.log("Currency pairs:", response); }, function(error) { console.error("Error:", error); });Get Client List (Admin)
Section titled “Get Client List (Admin)”API.admin.post( "client/get_list", { per_page: 10, page: 1 }, function(response) { console.log("Clients:", response.list); }, function(error) { console.error("Failed to load clients:", error); });Disable the Spinner
Section titled “Disable the Spinner”API.admin.post( "client/get_list", {}, function(response) { console.log(response.list); }, function(error) { console.error(error.message); }, false);Loading Spinner
Section titled “Loading Spinner”The wrapper automatically creates a loader element for requests when the spinner is enabled. To use it, make sure your theme has styling for .spinner-border.
The wrapper creates and removes the spinner element automatically:
.spinner-border { display: inline-block; animation: spin 1s linear infinite;}
Disabling the Spinner
Section titled “Disabling the Spinner”Either:
- Don't define a
.spinner-borderclass in your CSS - Pass
falseas the fifth parameter to the API call like below:
API.admin.post('client/get_list', {}, onSuccess, onError, false);API Forms and Links
Section titled “API Forms and Links”The Twig helpers fb_api_form() and fb_api_link() render a data-fb-api attribute that the wrapper uses for automatic form and link handling.
Use api_url for the target endpoint:
<form action="{{ 'profile/update'|api_url }}" {{ fb_api_form({ message: 'Profile updated'|trans }) }}> <input type="text" name="first_name" value="{{ profile.first_name }}"> <button type="submit">{{ 'Save'|trans }}</button></form>fb_api_form() adds method="post" and data-fb-api. The wrapper serializes the form, adds the CSRF token automatically for session-authenticated calls, disables enabled buttons while the request is running, and shows errors with FOSSBilling.message().
For API links:
<a href="{{ 'client/delete'|api_url(query: { id: client.id }, role: 'admin') }}" {{ fb_api_link({ modal: { type: 'confirm', title: 'Are you sure?'|trans }, redirect: 'client'|url }) }}> {{ 'Delete'|trans }}</a>The wrapper intercepts links with data-fb-api, sends a GET request to the link href, and then handles the configured follow-up action.
Supported data-fb-api options:
| Option | Applies to | Description |
|---|---|---|
message | Forms and links | Show a success toast after the request. |
redirect | Forms and links | Redirect to the given URL after success. |
reload | Forms and links | Pass true to reload the current page after success. |
callback | Forms and links | Name of a global window function to call with the API result. |
params | Links | Extra request parameters merged into the link request. |
modal | Links | Show a confirmation, danger, or prompt modal before the request. |
Prompt modals require a key; the entered value is sent under that request parameter name:
<a href="{{ 'example/update_name'|api_url }}" {{ fb_api_link({ modal: { type: 'prompt', title: 'New name'|trans, label: 'Name'|trans, key: 'name' }, reload: true }) }}> {{ 'Rename'|trans }}</a>Handling Responses
Section titled “Handling Responses”Success Response
Section titled “Success Response”The success callback receives the decoded API response object:
function(response) { console.log(response);}Error Response
Section titled “Error Response”The error callback receives the API error payload:
function(error) { console.error(error.message, error.code);}The wrapper passes the API error's message and code to onError, including on non-200 responses. Custom wrappers must check both the HTTP status and the JSON error field.
Manual Requests
Section titled “Manual Requests”Use manual fetch() only when the wrapper does not fit. Include the CSRF token for session-authenticated admin/client calls and protected guest authentication/cart calls. Use the JSON body, form data, or X-CSRF-Token header; query tokens are not accepted for guest authentication:
function getCsrfToken() { const match = document.cookie.match(/fossbilling_csrf=([^;]*)/); return match ? decodeURIComponent(match[1]) : '';}
fetch('{{ "profile/update"|api_url }}', { method: 'POST', headers: { 'Accept': 'application/json', 'Content-Type': 'application/json', 'X-CSRF-Token': getCsrfToken(), }, body: JSON.stringify({ first_name: 'Jane' }),});Complete Example
Section titled “Complete Example”function loadProfile() { API.client.get( "profile/get", {}, function(profile) { document.getElementById('name').textContent = profile.first_name + ' ' + profile.last_name; document.getElementById('email').textContent = profile.email; }, function(error) { alert('Failed to load profile: ' + error.message); } );}
document.addEventListener('DOMContentLoaded', loadProfile);For more details, see the API Reference.