Summary
The Flick Server API lets Minecraft servers (the FlickServerAPI Paper plugin,
server-api/) recognise players who use Flick Client and cooperate with it: add waypoints, switch off selected Flick modules (they are greyed out in the menu with "Disabled by the server" and cannot be re-enabled until the player leaves), send notifications, set the Discord Rich Presence text and ask for the client's mod list. Communication runs over theflick:apiplugin message channel (JSON). The client only introduces itself (hello) to servers that registered that channel and acts on server commands only after the server'shelloreply. Everything the server sets lives in memory only and disappears when the player leaves or switches server. A server cannot run code, open URLs, read files or touch the account or cosmetics. Players can turn the whole API off ("Allow the Flick server API" in the client settings) or single categories. The mod list and module state are reported by the client and can be forged, so this is not an anti-cheat. No-code setup:plugins/FlickServerAPI/config.yml(globally or per-world disabled modules, welcome message,kick-if-mods). Commands:/flick list,/flick info <player>,/flick reload.
#Overview
| Capability | Server → client | Player can refuse |
|---|---|---|
| Detect Flick players, client & MC version, enabled modules | hello, state (client → server) |
only by turning the whole API off |
| Waypoints (beam, icon, colour, auto-expiry, remove-on-arrival) | waypoint_add / waypoint_remove / waypoint_clear |
yes (Server waypoints) |
| Force Flick modules off while on the server | modules / modules_reset |
only by turning the whole API off |
| Glass notifications (toasts) | notify |
yes (Server notifications) |
| Discord Rich Presence text | rpc / rpc_reset |
yes (Server Discord status), and only shown when the player shows the server in their status |
| Loaded mod list (client-reported) | mods_request → mods |
yes (Share mod list) — the client then answers denied:true |
| Clear everything the server set | reset |
— |
Client side: mod/src/main/java/net/flick/client/api/ (Flick Client, all MC versions 1.21.4 → 26.2).
Server side: server-api/ — Paper plugin FlickServerAPI (Paper 1.21.x, Java 21).
#Installing the plugin
- Build:
cd server-api && ./gradlew build→server-api/build/libs/FlickServerAPI-1.0.0.jar. - Copy the jar into
plugins/, start the server once, editplugins/FlickServerAPI/config.yml,/flick reload.
Works behind Velocity/BungeeCord: install the plugin on each backend server; plugin messages on flick:api pass
through the proxy. Every backend switch is a new session (the client sends a new hello; state from the previous
backend is discarded by the client).
#Using it from another plugin
plugin.yml of your plugin:
depend: [FlickServerAPI] # or softdepend, and check Bukkit.getPluginManager().isPluginEnabled("FlickServerAPI")
Gradle (until the API is published to a Maven repository, compile against the jar):
dependencies {
compileOnly files("libs/FlickServerAPI-1.0.0.jar")
}
#Java API
Package net.flick.serverapi. Obtain the instance with FlickAPI.get() (also registered in Bukkit's
ServicesManager as FlickAPI). Call it on the main thread (calls from other threads are re-scheduled onto it).
Every method that targets a player returns false (or a failed future) when the player is not a Flick player, so you
can call them for everyone.
import net.flick.serverapi.*;
import net.flick.serverapi.event.*;
public final class Arena implements Listener {
private final FlickAPI flick = FlickAPI.get();
@EventHandler
public void onFlickJoin(FlickPlayerJoinEvent e) {
FlickPlayer c = e.getClient();
getLogger().info(e.getPlayer().getName() + " uses Flick " + c.getVersion() + " on MC " + c.getMinecraftVersion()
+ ", modules: " + c.getEnabledModules());
}
public void startRankedMatch(Player p, Location redFlag) {
// greyed out in the mod menu ("Disabled by the server"), cannot be enabled until the player leaves
flick.disableModules(p, FlickModules.COMPETITIVE, "Ranked match");
flick.addWaypoint(p, FlickWaypoint.builder("ctf_red_flag")
.name("Red flag")
.location(redFlag) // sets the dimension from the world
.color(0xFF5555)
.icon(WaypointIcon.FLAG)
.build());
flick.addWaypoint(p, FlickWaypoint.builder("spawn_hint")
.name("Go here")
.position(10.5, 64, -20.5)
.world(p.getWorld())
.temporary(true) // disappears when the player gets within 4 blocks
.duration(Duration.ofMinutes(2)) // or after 2 minutes
.build());
flick.sendNotification(p, "Match found", "Good luck!", NotificationKind.SUCCESS, Duration.ofSeconds(6));
flick.setRichPresence(p, "Ranked CTF", "Red team");
}
public void endMatch(Player p) {
flick.reset(p); // waypoints, API-disabled modules and presence; config policy is re-applied
}
public void checkMods(Player p) {
flick.requestMods(p).thenAccept(ids -> { // completes on the main thread
if (ids.contains("freecam")) p.kick(Component.text("Freecam is not allowed"));
}).exceptionally(err -> null); // TimeoutException after 10 s, IllegalStateException if not Flick
}
}
#Reference
| Method | Notes |
|---|---|
boolean isFlick(Player) |
handshake completed |
Optional<FlickPlayer> getClient(Player), Collection<FlickPlayer> getClients() |
|
boolean addWaypoint(Player, FlickWaypoint) |
same id replaces; max 200 per player (returns false beyond) |
boolean removeWaypoint(Player, String id), boolean clearWaypoints(Player) |
|
boolean disableModules(Player, Collection<String> ids, String reason) |
adds to the API-disabled set; ids from FlickModules |
boolean enableModules(Player, Collection<String> ids), boolean resetModules(Player) |
config-disabled modules stay disabled |
boolean sendNotification(Player, title, body[, NotificationKind, Duration]) |
title ≤ 64, body ≤ 160 chars, 1–15 s |
boolean setRichPresence(Player, details, state), boolean clearRichPresence(Player) |
≤ 64 chars each |
CompletableFuture<List<String>> requestMods(Player) |
client-reported, spoofable; empty list if the player refused |
boolean reset(Player) |
clears everything this server set (config module policy is re-sent) |
FlickPlayer: getVersion(), getMinecraftVersion(), getProtocolVersion(), getEnabledModules() (live, via
state), getCapabilities(), allows(FlickFeature), getWaypointIds(), getDisabledModules(),
getLastReportedMods(), getHandshakeTime().
Events (main thread): FlickPlayerJoinEvent (after the handshake; config modules and welcome already sent),
FlickPlayerQuitEvent.
Validation: FlickWaypoint.builder throws IllegalArgumentException for an invalid id ([A-Za-z0-9_.:-]{1,64}),
non-finite / out-of-world coordinates (|x|,|z| ≤ 30 000 000, −2048 ≤ y ≤ 4096), invalid dimension keys or a duration
over 24 h; module ids must match [a-z0-9_]{1,32}. Strings are stripped of § formatting codes and control characters
and cut to their limits.
#config.yml (no-code setup)
server-name: "My Server" # shown under notifications (≤ 32 chars)
disabled-modules:
global: [freelook] # forced off for every Flick player
worlds: # additionally, per world folder name
pvp_arena: [fullbright, hitboxes, zoom]
reason: "Disabled on this server" # shown in the mod menu / toast
welcome:
enabled: true
title: "Welcome, {player}!"
body: "This server supports Flick Client."
kind: info # info | success | warning | error
duration-ms: 6000
kick-if-mods: # asks for the mod list right after the handshake
enabled: false
mods: [freecam, xaerominimap] # Fabric mod ids (lower case)
message: "Disallowed client mods: {mods}"
rate-limit: # inbound messages per player
messages-per-second: 20
burst: 40
debug: false
The module set a player receives is global ∪ worlds[current world] ∪ modules disabled through the API; it is
re-sent on world change and on /flick reload.
#Commands
| Command | Permission | |
|---|---|---|
/flick list |
flick.admin (op) |
Flick players online, with client and MC versions |
/flick info <player> |
flick.admin |
version, protocol, enabled modules, server-disabled modules, waypoint count, refused categories, reported mods |
/flick reload |
flick.admin |
reload config.yml and re-apply module policy |
#Disableable module ids
Generated from the client's module registry at runtime (Flick Client 0.1.0; DevShots [flick-api-test] MODULES).
Only toggleable modules can be disabled; client (settings), theme and friends are always on. Unknown ids are
ignored by the client. Constants: net.flick.serverapi.FlickModules (ALL, COMPETITIVE).
| id | Name |
|---|---|
fps |
FPS |
cps |
CPS |
keystrokes |
Keystrokes |
armor_status |
Armor Status |
potion_effects |
Potion Effects |
coordinates |
Coordinates |
ping |
Ping |
clock |
Clock |
server_address |
Server Address |
memory |
Memory |
saturation |
Saturation |
toggle_sprint |
Toggle Sprint |
hit_color |
Hit Color |
old_animations |
1.7 Animations |
reach_display |
Reach Display |
combo_counter |
Combo Counter |
target_hud |
Target HUD |
low_fire |
Low Fire |
hitboxes |
Hitboxes |
zoom |
Zoom |
fullbright |
Fullbright |
crosshair |
Custom Crosshair |
block_outline |
Block Outline |
time_changer |
Time Changer |
weather_changer |
Weather Changer |
particles |
Hit Particles |
freelook |
Freelook |
auto_gg |
Auto GG |
nick_hider |
Nick Hider |
address_hider |
Server Address Hider (replaces the server address on screen; a server may want it off so the address stays visible in screenshots) |
screenshots |
Screenshots |
waypoints |
Waypoints (player's own) |
chat_tweaks |
Chat Tweaks |
inventory_blur |
Inventory Blur |
item_physics |
Item Physics |
emotes |
Emotes |
perf_panel |
Performance panel |
aspect_ratio |
Aspect Ratio |
viewmodel |
ViewModel |
perspective |
Perspective (hold F4 for a free orbit camera; the scroll wheel then changes the camera distance up to 64 blocks, and an optional no-collision mode sees through walls, so competitive servers should disable it) |
hurt_cam |
Hurt Cam |
clear_water |
Clear Water |
fast_chest |
Fast Chest |
hit_sounds |
Hit Sounds |
swing_animations |
Swing Animations |
totem_pop |
Totem Pop (custom totem animation on screen) |
totem_counter |
Totem Counter (totem pops per player next to the nametag, icon for a held totem, own totem count on the HUD, optional pop message; purely client-side bookkeeping of events the server already sends) |
cooldown_hud |
Cooldown HUD (active item cooldowns, incl. event items, with the real remaining time; only shows data the client already has) |
combat_fps |
Combat FPS (fewer explosion/totem particles, smaller totem pop and blocking shield; client-side performance only) |
hit_bubbles |
Hit Bubbles |
hit_marker |
Hit Marker |
damage_numbers |
Damage Numbers (shows health changes of nearby entities) |
health_indicators |
Health Indicators (hearts above other players' heads showing their synced health; an information advantage, part of COMPETITIVE) |
color_saturation |
Color Saturation (post effect: saturation, vibrance, contrast, brightness, tint, presets; skipped under an Iris shader pack) |
motion_blur |
Motion Blur (post effect) |
kill_effects |
Kill Effects (client-side effects on deaths) |
target_esp |
Target ESP (marker and card on your current opponent; optional through-walls mode) |
jump_circles |
Jump Circles (glowing rings on jump/landing; optionally under other players) |
trails |
Trails (glowing trail behind you; optionally other players, arrows, ender pearls) |
china_hat |
China Hat (translucent glowing hat / halo above the head) |
aura |
Aura (ambient particles around the player) |
trajectories |
Trajectories (predicted path of bows, crossbows, tridents and thrown items; an aiming aid) |
ambience |
Ambience (client-side sky and fog tint) |
glint_color |
Glint Color (enchantment glint tint) |
shulker_preview |
Shulker Preview (tooltip grid with the contents of shulker boxes and other containers; client-side, from item data) |
title_tweaks |
Title Customization (scale, position, shadow, hiding and timing of /title titles and the action bar) |
bossbar_tweaks |
Boss Bar (scale, position, hiding and compact spacing of boss bars) |
scoreboard_tweaks |
Scoreboard (sidebar scale, position, background, hidden red score numbers, title and line limit; purely cosmetic) |
nametags |
Nametags (scale, vertical offset, background, shadow, distance fade, own nametag in third person; never changes which nametags vanilla shows) |
friends_ingame |
Friends in game (Flick friends highlighted in the tab list and on nametags; party HUD of friends on the same server, with health only when the friend is already a visible entity) |
Removed module
camera_distance. The scroll-wheel camera distance (formerly a separate F5 module) is now part ofperspective; F5 is vanilla again. Servers that disabledcamera_distanceshould disableperspectiveinstead - the old id is ignored by the client.FlickModules.COMPETITIVEnow containsPERSPECTIVEinstead ofCAMERA_DISTANCE.
Removed module
minimap. The minimap and the world map (M) were removed from Flick Client. The id is ignored by the client;FlickModules.MINIMAPis kept as@Deprecated(no effect) so existing plugins still compile, and it is no longer inFlickModules.ALLorFlickModules.COMPETITIVE.
Disabling waypoints hides the player's own waypoints; server waypoints are controlled separately by the player's
Server waypoints setting.
#Protocol specification
Protocol version 1. Implement this to support Flick from a non-Paper server (Fabric, Minestom, a proxy...).
#Transport
- Minecraft custom payload (plugin message) on channel
flick:api, play phase only. - Body = one UTF-8 JSON object, no length prefix (exactly the
byte[]of Bukkit'ssendPluginMessage/onPluginMessageReceived). Every object has a string type field"t"(≤ 32 chars). - Max 32 767 bytes per message in both directions. Larger messages are ignored (never a disconnect).
- Unknown
tvalues and unknown fields are ignored → forward compatible. Bumpprotoonly for breaking changes. - The client registers
flick:apiwithminecraft:register(Fabric does this automatically). Bukkit only delivers messages to clients that registered the channel.
#Handshake and session
- When joining, the client checks whether the server registered
flick:api(Paper/Bukkit announce every registered incoming channel inminecraft:registeron join). Only then does it sendhello. A server without the plugin therefore never receives anything. - The server answers
hello. Until that answer, the client ignores every server message. - The session ends on disconnect, on a new play session (server switch behind a proxy), or when the player turns the API off. The client then discards everything the server set: waypoints, disabled modules (restored to the player's own choice), Rich Presence. Nothing server-set is ever written to disk.
#Client → server
| t | Fields |
|---|---|---|
| hello | proto (int, 1), client ("flick"), version (Flick version, e.g. 0.1.0+1.21.11), mc (1.21.11), modules (enabled module ids), caps (supported families: waypoints, modules, notify, rpc, mods, reset), allow ({waypoints, notify, rpc, mods} booleans — player's per-category choices) |
| state | modules (effectively enabled module ids) |
| mods | nonce (echo of the request), ids (loaded mod ids, ≤ 512), denied (true if the player refused; ids empty) |
Example (real hello captured from the e2e test):
{"t":"hello","proto":1,"client":"flick","version":"0.1.0+1.21.11","mc":"1.21.11",
"modules":["fps","cps","keystrokes","zoom","crosshair","waypoints"],
"caps":["waypoints","modules","notify","rpc","mods","reset"],
"allow":{"waypoints":true,"notify":true,"rpc":true,"mods":true}}
#Server → client
| t | Fields (limits) |
|---|---|---|
| hello | proto (int ≥ 1), server (name, ≤ 32) |
| waypoint_add | id ([A-Za-z0-9_.:-]{1,64}), name (≤ 48), x y z (finite numbers; |x|,|z| ≤ 3·10⁷, −2048 ≤ y ≤ 4096), dimension (minecraft:overworld default; ns:path), color ("#RRGGBB" or int 0xRRGGBB), icon? (pin flag sword star cube globe skull heart users lock arrow chest), beam? (default true), temporary? (remove within 4 blocks), durationMs? (0–86 400 000, 0 = no expiry) | add / replace by id; max 200 (extra ids ignored); invalid → ignored |
| waypoint_remove | id |
| waypoint_clear | — |
| modules | disable (module ids, ≤ 128), reason? (≤ 96) |
| modules_reset | — |
| notify | title (≤ 64, required), body (≤ 160), kind (info/success/warning/error), durationMs (1000–15 000, default 5000) |
| rpc | details (≤ 64), state (≤ 64); both empty = clear |
| rpc_reset | — |
| mods_request | nonce? ([A-Za-z0-9_-]{1,32}) |
| reset | — |
All strings are sanitised on the client: § formatting codes, control, bidi/format and private-use characters are
removed, whitespace collapsed, length cut to the limit.
#Rate limits
- Client accepts server messages with a token bucket of 40 msg/s, burst 100; excess is dropped (logged once). Send bulk waypoint updates in batches (e.g. ≤ 30 per tick-second).
- The plugin accepts client messages at 20 msg/s, burst 40 (configurable); it accepts nothing but
hellobefore the handshake.
#Security model
- No code, no URLs, no files, no accounts. The protocol has no message that runs code, opens a link or a screen, executes commands/chat, reads or writes files, or touches the Mojang/Microsoft account, Flick account, friends or cosmetics. Text is only ever displayed.
- Opt-in per server, gated by the handshake. The client never talks first to servers that do not register
flick:api, and obeys nothing until the server answershello. - Session-only. Waypoints, module blocks and presence live in memory and are cleared on disconnect / server switch / when the player disables the API. The player's saved configuration is never modified (a module blocked by the server keeps the player's own on/off choice and returns to it).
- Bounded. Size limit 32 KiB, string limits, 200 waypoints, 128 module ids, token-bucket rate limits on both sides, notification and mod-request throttles. Malformed input is ignored, never fatal.
- Player control. Allow the Flick server API (master switch, default on) and per-category switches: server waypoints, server notifications, server Discord status, share mod list. With the master switch off the client does not identify itself at all.
- Client-reported data is untrusted.
hello,stateandmodscome from the client; a modified client can lie or omit anything. Module disabling is cooperative (honest Flick clients obey it) — use it for fair-play UX, not as an anti-cheat. The same applies tokick-if-mods. - Privacy. The mod list is only sent on request, only to a server that completed the handshake, and only if the player allows it.
#Testing
Plugin unit tests (MockBukkit 4.116.3, Paper API 1.21.11):
cd server-api && ./gradlew test— handshake, gating, waypoint/module/notification/presence messages, config policy (global/per world), welcome, mod requests (reply, denial, timeout, quit), kick-if-mods, malformed input, rate limiting, commands, golden wire format.Client end-to-end without Paper: a dev-only shim inside the integrated server speaks the same protocol (
net.flick.client.api.dev.ApiDevShim, enabled only with-Dflick.apishim=trueor the DevShots suite):cd mod ./gradlew :1.21.11:runClient -Pdevshots -Pdevsuite=api -Pdevres=1920x1080The suite creates a world, verifies the handshake and gating, adds waypoints, disables modules, sends a notification and presence, requests mods, sends malformed / oversized / flood traffic, leaves the world and verifies the reset. Results:
[flick-api-test] PASS|FAIL …lines andSUMMARYin the log; screenshotsrun/screenshots/flick_api_{hud,menu_blocked,settings_blocked,after_leave}_*.png.Manual testing against a real Paper server: install the plugin jar, join with Flick,
/flick list.
