> ## Documentation Index
> Fetch the complete documentation index at: https://andcze.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Plugin protocol

> The Socket.IO contract for your own fulfillment plugin: authentication, events, atomic claim and safe acknowledgement.

This page is for people writing **their own fulfillment plugin** (e.g. for an unusual platform). If you only want to install the official plugin on your server, go to [Plugin on your server](https://andcze.mintlify.site/en/shop/plugin.md).

The plugin connects to the Socket.IO namespace `/plugin` on the public API origin. The game server always initiates the connection — it exposes no inbound port.

> [!WARNING]
>
> The old REST polling (`GET /orders/plugin/.../pending`, `POST /orders/plugin/.../complete`) has been retired. It offered no atomic claim across multiple instances and no protection for the ambiguous failure window. Use only the protocol below.

## Authentication

The namespace handshake sends:

```json theme={null}
{ "apiKey": "isk_<64 hex chars>", "serverId": "<24 hex chars>" }
```

The backend accepts the connection only when:

* the key is active, of type `plugin` and has `orders:write`;
* the key is bound to a shop and `serverId` belongs to the same shop;
* the key's creator still has access to the shop.

Any failure ends with a generic `unauthorized` — without revealing which condition failed. Create the key in the panel (preset **Minecraft plugin**), see [API with a key](https://andcze.mintlify.site/en/developers/api-keys.md).

## Events (protocol v1)

| Direction | Event | Data / ACK |
| - | - | - |
| plugin → app | `hello` | `{ platform, version }` |
| app → plugin | `ready` | `{ serverId, serverName }` |
| app → plugin | `order:deliver` | `{ v: 1, id, player, status, commands[], requireOnline }` |
| plugin → app | `order:claim` | `{ orderId }`; the ACK returns a fresh command snapshot to the claim winner |
| plugin → app | `order:release` | voluntarily give up a claim when the fresh delivery condition isn't met |
| plugin → app | `order:verify` | re-check the current status of a delivery that has waited a long time |
| plugin → app | `order:complete` | `{ orderId }`; only the ACK allows removing the local `AWAITING_ACK` entry |
| plugin → app | `order:uncertain` | report a recovered `EXECUTING`; reply: `manual-review`, `retry` or `complete` |
| plugin → app | `orders:pull` | rate-limited request for a full backlog replay |
| plugin → app | `players:update` | `{ online, max, players?[] }`; the backend filters and bounds the values |
| plugin → app | `ping:app` | diagnostic ACK with the app time |

The backend sends a full backlog replay after every connection and a push when an order is paid or changes status. **A repeated `order:deliver` is normal** — the right to execute is decided by the atomic claim and the local journal, not by receiving the event.

## Safety states

The plugin must keep a local journal written with `fsync`:

**1. Claim**

`order:claim` moves the order from `paid` or `dispute` to `processing`. Only one instance wins the conditional database update.

**2. EXECUTING**

Write and sync `EXECUTING` **before** the first command.

**3. AWAITING\_ACK**

After the last command, write and sync `AWAITING_ACK`.

**4. Complete**

`order:complete` moves the order to `completed`. Remove the local entry only after the ACK.

After a restart:

* an `AWAITING_ACK` entry → retry **only** `order:complete`, not the commands;
* an `EXECUTING` entry → **don't** repeat the commands; send `order:uncertain`, which starts a manual review.

> [!NOTE]
>
> Don't delete the journal to "unblock" an order. Check the player's actual state in the panel and resolve the entry manually: `retry` clears the claim on the app side and allows a new push, `complete` accepts the delivery without repeating commands.

## Commands and placeholders

The API formats product commands before sending the fresh snapshot to the claim winner. The plugin receives ready commands — it substitutes nothing itself.

| Placeholder | Replaced with |
| - | - |
| `{PLAYER}` / `{NICK}` | the order's verified nickname |
| `{QUANTITY}` | the quantity |
| `{PRODUCT}` | the product name |
| `{QUANTITY*1.5:2}` | an allowed math expression, rounded to 2 decimal places |

Expressions are evaluated by a restricted parser — never executed as code. `quantityTiers` thresholds, the command variant and the package bonus are resolved by the API. The plugin should additionally reject commands whose first word is in `blockedCommands`.

## Player online

When `requireOnline` is set, run the commands only after the correct nickname is detected on an allowed server. A local copy that waited a long time must be checked with `order:verify`, so a cancelled or refunded order isn't executed from a stale event. If the delivery condition isn't met after claiming, give the claim back with `order:release`.

## Next

> **[Plugin on your server](https://andcze.mintlify.site/en/shop/plugin.md)**
>
> Install and configure the official plugin.

> **[Orders](https://andcze.mintlify.site/en/shop/orders.md)**
>
> Order statuses and manual review in the panel.

> **[API with a key](https://andcze.mintlify.site/en/developers/api-keys.md)**
>
> `plugin` keys and their scope.

> **[Errors and limits](https://andcze.mintlify.site/en/developers/errors-and-limits.md)**
>
> REST limits and retries.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.