> For the complete documentation index, see [llms.txt](https://docs.xandtech.fr/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.xandtech.fr/home/xplayercurrencies/developer/api.md).

# API & Events

XPlayerCurrencies exposes a stable Java API for other plugins to read/change balances, plus a cancellable event fired on every balance change.

## Getting the API

```java
XPlayerCurrencies plugin = (XPlayerCurrencies) Bukkit.getPluginManager().getPlugin("XPlayerCurrencies");
XPlayerCurrenciesAPI api = plugin.getApi();
```

Add XPlayerCurrencies as a dependency in your `plugin.yml` (`depend: [XPlayerCurrencies]` or `softdepend` if the integration is optional).

## XPlayerCurrenciesAPI

Every balance change made through this class goes through the **exact same path** as a player typing a command: it's clamped to the currency's `min-balance`/`max-balance`, logged in the transaction history, and fires a cancellable `CurrencyBalanceChangeEvent` first (a cancelled change returns the balance unchanged). `give`/`take`/`set`/`reset` return a `CompletableFuture<Double>` rather than a callback, resolving to the resulting balance.

```java
List<String> getCurrencyIds();
boolean hasCurrency(String currencyId);
boolean isCurrencyEnabled(String currencyId);

// Fast, non-blocking - accurate for an online player, falls back to the currency's default
// balance for an offline one or an unknown currency. Safe to call every tick.
double getCachedBalance(UUID uuid, String currencyId);

// Works for any player, online or not.
CompletableFuture<Double> getBalance(UUID uuid, String currencyId);

CompletableFuture<Double> give(UUID uuid, String currencyId, double amount, String reason);
CompletableFuture<Double> take(UUID uuid, String currencyId, double amount, String reason);
CompletableFuture<Double> set(UUID uuid, String currencyId, double amount, String reason);
CompletableFuture<Double> reset(UUID uuid, String currencyId, String reason);
```

`reason` is a short, all-caps-by-convention tag of your own choosing (e.g. `"QUEST_REWARD"`) - shown as the action in the logs menu and on `CurrencyBalanceChangeEvent#getReason()`. Every `give`/`take`/`set`/`reset` call completes exceptionally with `IllegalArgumentException` if `currencyId` doesn't exist or is disabled.

### Example

```java
api.give(player.getUniqueId(), "coins", 500, "QUEST_REWARD")
   .thenAccept(newBalance -> player.sendMessage("New balance: " + newBalance));
```

## Formatting & display metadata

Everything needed to label/format a balance without touching the plugin's internal `Currency` class:

```java
// Empty if currencyId doesn't exist - does NOT require the currency to be enabled.
Optional<CurrencyInfo> getCurrencyInfo(String currencyId);

// "1,000 Coins" or "$1,000" depending on symbol position - the exact formatting a player sees.
// Falls back to the bare number if currencyId doesn't exist (never throws).
String format(String currencyId, double amount);

// "1,000" - the raw number only, no symbol.
String formatNumber(String currencyId, double amount);
```

`CurrencyInfo` is a record: `id`, `nameDisplay`, `nameDisplayPlural`, `icon`, `symbol`, `symbolBeforeAmount`, `decimals`, `defaultBalance`, `minBalance`, `maxBalance`, plus `hasMaxBalance()` (`false` means `maxBalance` is unlimited, i.e. `-1`).

## Bank

A currency's separate, interest-earning balance (see [Bank](/home/xplayercurrencies/features/bank.md)). Every method works for an online or offline player and - exactly like `give`/`take`/`set`/`reset` above bypass wallet min/max as an explicit override - these bypass the bank's own tier max-balance cap too:

```java
CompletableFuture<Double> getBankBalance(UUID uuid, String currencyId);

CompletableFuture<Double> giveBank(UUID uuid, String currencyId, double amount, String reason);
CompletableFuture<Double> takeBank(UUID uuid, String currencyId, double amount, String reason);
CompletableFuture<Double> setBank(UUID uuid, String currencyId, double amount, String reason);

// Resets both the balance AND the tier back to 0 - same as "/currencies admin bankreset".
CompletableFuture<Double> resetBank(UUID uuid, String currencyId, String reason);
```

{% hint style="info" %}
Unlike the wallet methods, `reason` here is logged as the transaction's **actor** (the "By: ..." column in the logs menu) rather than as the action itself - the bank's log always uses a fixed `BANK_ADMIN_<action>` tag so it stays grouped under the existing "Bank" filter. Defaults to `"API"` if blank. Bank mutations don't fire `CurrencyBalanceChangeEvent` (only wallet ones do).
{% endhint %}

## Boosters

Temporary or permanent earnings/discount bonuses - see [Boosters](/home/xplayercurrencies/features/boosters.md). `Currency.BoosterScope` is `BUY` (shop purchase discount), `SELL` (shop/sellall/sell-stick bonus), `KILL` (kill coins bonus), or `ALL` (all three):

```java
// Every active boost %, summed (global + the player's own + every permission booster they
// hold) - requires an ONLINE player (permission boosters are checked live).
double getBoostPercent(Player player, String currencyId, Currency.BoosterScope scope);

// Same as "/currencies admin booster event"/"give" - both return false if currencyId doesn't
// exist or is disabled.
boolean addGlobalBoost(String currencyId, Currency.BoosterScope scope, double percent, int durationMinutes);
boolean addPlayerBoost(UUID uuid, String currencyId, Currency.BoosterScope scope, double percent, int durationMinutes);

// Ends every active GLOBAL boost for currencyId (every scope if scope is null) - never touches
// a per-player or permission booster. Returns how many were actually stopped.
int stopGlobalBoosts(String currencyId, Currency.BoosterScope scope);
```

## CurrencyBalanceChangeEvent

Fired right before **any** player's balance for **any** currency actually changes - a command, the Vault economy bridge, `XPlayerCurrenciesAPI`, or the built-in [fraud detection](/home/xplayercurrencies/features/fraud-detection.md) system (which cancels this event to block a suspicious gain). Cancel it yourself to veto a change entirely - nothing is saved and nothing is logged.

```java
public class MyListener implements Listener {
    @EventHandler
    public void onBalanceChange(CurrencyBalanceChangeEvent event) {
        if (event.getReason().equals("SHOP_SELL") && event.getAmount() > 10_000) {
            event.setCancelled(true);
        }
    }
}
```

| Method                                    | Description                                                                                                                                                                                                                                                                                                                                                                                                                                                 |
| ----------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `getPlayerUuid()`                         | The affected player.                                                                                                                                                                                                                                                                                                                                                                                                                                        |
| `getCurrencyId()`                         | The currency's id (`currencies/<id>.yml` filename).                                                                                                                                                                                                                                                                                                                                                                                                         |
| `getOldBalance()` / `getNewBalance()`     | Before/after (already clamped to min/max) - `getNewBalance()` is what will actually be saved if not cancelled.                                                                                                                                                                                                                                                                                                                                              |
| `getAmount()`                             | `getNewBalance() - getOldBalance()`.                                                                                                                                                                                                                                                                                                                                                                                                                        |
| `getReason()`                             | Not a fixed enum - common values include `GIVE`, `TAKE`, `SET`, `RESET`, `PAY_SENT`/`PAY_RECEIVED`, `CHEQUE_CREATED`/`CHEQUE_REDEEMED`, `VAULT_DEPOSIT`/`VAULT_WITHDRAW`, `EXCHANGE_OUT`/`EXCHANGE_IN`, `SHOP_BUY`/`SHOP_SELL`, `BANK_DEPOSIT`/`BANK_WITHDRAW`/`BANK_UPGRADE`/`BANK_INTEREST`, `DEATH_PENALTY`, `KILL_COINS`, `BLACK_MARKET_BUY`, `API`, or whatever your own plugin passed in. Treat an unrecognized value as "something else changed it". |
| `isCancelled()` / `setCancelled(boolean)` | Standard Bukkit `Cancellable`.                                                                                                                                                                                                                                                                                                                                                                                                                              |

{% hint style="info" %}
This event can fire from either the main thread or an async task, depending on whether the affected player is online (main thread) or being modified while offline (async). `isAsynchronous()` reports this correctly - check it before touching any other Bukkit API from your handler.
{% endhint %}

Next: back to [XPlayerCurrencies overview](/home/xplayercurrencies/readme.md).


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://docs.xandtech.fr/home/xplayercurrencies/developer/api.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
