# Addon API

Route and observe relayed voice connections from your own Velocity or BungeeCord plugin

Source: https://socketsvc.vercel.app/proxy/api
The relay plugin exposes a small Java API for other proxy plugins. It is built as `socketsvc-proxy-<version>-api.jar` by `buildAllProxy`, alongside the plugin jar itself.

<Callout title="Routing and observation only">
  The relay is a byte-level pipe — it never terminates the WebSocket handshake and never decodes audio. So this API covers **which backend a player is relayed to** and **what the relay is doing**, and nothing else. Anything about the voice itself (speakers, positions, per-player volume) belongs to the backend's own [Spatial audio API](/server/spatial-audio-api). No such API can exist on the relay.
</Callout>

Generated reference for every type: [Proxy API](/api/proxy/SocketSvcProxyApi).

## Depending on it

Compile against the API jar and never shade it — the same rule the Paper plugin's API jar follows. Shading it would put a second copy of the interfaces on the classpath, and the relay's own registration would no longer be the one your plugin sees.

Every release publishes the API jar to the update service's Maven repository, so you do not have to download it or commit a copy:

```kotlin title="build.gradle.kts"
repositories {
    maven("https://socketsvc-updater.vercel.app/maven") {
        name = "SocketSVC"
        // Nothing else is published there, so no other lookup is slowed down by it.
        content { includeGroup("com.socketsvc") }
    }
}

dependencies {
    compileOnly("com.socketsvc:socketsvc-proxy-api:0.2.0")
}
```

```xml title="pom.xml"
<repositories>
  <repository>
    <id>socketsvc</id>
    <url>https://socketsvc-updater.vercel.app/maven</url>
  </repository>
</repositories>

<dependencies>
  <dependency>
    <groupId>com.socketsvc</groupId>
    <artifactId>socketsvc-proxy-api</artifactId>
    <version>0.2.0</version>
    <scope>provided</scope>
  </dependency>
</dependencies>
```

The version is the relay's own `proxy.version`, and every published build has one — including dev builds, as `0.2.1-dev.7`. Resolve the version your server actually runs: the API a release serves is the API that release's relay implements. `compileOnly(files("libs/socketsvc-proxy-<version>-api.jar"))` still works if you would rather vendor the jar.

## Getting the API

Neither Velocity nor BungeeCord has a Bukkit-style services manager, so the API is published statically. It is `null` before the relay plugin has started and `null` again after the proxy shuts down, so look it up when you need it rather than caching it in a field.

```java
SocketSvcProxyApi api = SocketSvcProxyApi.get();
if (api == null) return;   // relay plugin not started
```

The same API and the same jar work on both platforms.

## Routing overrides

A [`ProxyBackendResolver`](/api/proxy/ProxyBackendResolver) decides which backend voice listener a connecting player reaches, overriding the `servers:` map in [the relay's config](/proxy/configuration#relay-configuration).

```java
api.setBackendResolver("my-addon", (playerId, serverName) -> {
    if (isInLobbyQueue(playerId)) return Optional.of(LOBBY_VOICE);
    return Optional.empty();   // defer: next resolver, then servers:
});
```

Resolvers are consulted in registration order, before the configured map. Returning `Optional.empty()` passes the decision on, and if every resolver passes, routing behaves exactly as it does with no addon installed.

<Callout type="warn" title="Runs on the relay's connection thread">
  A resolver is called once per incoming connection, before any bytes are forwarded, on the relay's own per-connection thread. Keep it fast and non-blocking — a slow resolver delays that player's voice connection — and make it safe to call from several threads at once.
</Callout>

A resolver is the **only** way to relay a player the proxy itself cannot place. When the `serverName` argument is empty, the proxy could not resolve the player to a backend — a race right after a server switch, or a backend missing from `servers:`. Without a resolver those connections are rejected; see [Failure behaviour](/proxy/commands#failure-behaviour).

## Lifecycle events

A [`ProxyRelayListener`](/api/proxy/ProxyRelayListener) observes connections opening, closing and being rejected. Every method has a default no-op, so override only what you need.

```java
api.addListener("my-addon", new ProxyRelayListener() {
    @Override public void onRelayOpened(RelaySession session) { }
    @Override public void onRelayClosed(RelaySession session) { }
    @Override public void onRelayRejected(UUID playerId, String reason) { }
});
```

Callbacks run on the relay's threads, never the proxy's main thread. `onRelayOpened` fires before the connection is piped and `onRelayClosed` as it is torn down, so do not block in them. In `onRelayRejected`, `playerId` is `null` when the connection never supplied a usable `uuid` parameter.

Each open and close carries a [`RelaySession`](/api/proxy/RelaySession): the player, the backend's name (or `null` when a resolver supplied an address without naming a server), the backend address, and a `nanoTime` stamp.

## Status and control

| Method | Reports |
| --- | --- |
| `isRunning()` | Whether the listener is accepting connections |
| `listenAddress()` | The single public address, or `null` when not running |
| `activeRelays()` | Connections currently being relayed |
| `rejectedConnections()` | Connections closed without being relayed since start |
| `backendVoiceAddresses()` | Snapshot of the configured `servers:` map |
| `isRelaying(UUID)` | Whether a player is currently being relayed |
| `reload()` | Re-reads `config.yml` and restarts the listener |

These are the same numbers [`/ssvc-proxy status`](/proxy/commands#admin-commands) prints, and `reload()` does what `/ssvc-proxy reload` does.

## Failure isolation and lifetime

An addon callback that throws is logged — naming the `ownerId` you registered with — and skipped. A resolver that throws falls through to the next one and then to the configured map; a listener that throws does not affect the connection. A broken addon degrades to default routing rather than breaking voice for the whole network.

Registrations are held by the plugin, not by the relay listener, so they survive both `/ssvc-proxy reload` and `reload()`. Call `clearOwner("my-addon")` from your own plugin's shutdown.

## Versioning

`SocketSvcProxyApi.API_VERSION` is incremented only for incompatible changes. Additions arrive as interface `default` methods, so an addon built against an older API jar keeps working.