> ## 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.

# Java & Kotlin SDK

> pl.itemshop:sdk (Maven) — Storefront and the API client for JVM 17+, built for Paper plugins and bots.

`pl.itemshop:sdk` is the official Java SDK (17+, `java.net.http`; the only dependency is **Gson**, which Paper and Spigot already ship). The `pl.itemshop:sdk-kotlin` module adds `suspend` functions and `Flow`.

| Class | What for | Secrets |
| - | - | - |
| `Storefront` | Storefront API v1: shop, products, payments, discounts, checkout, vouchers, order status, widgets | **none** — never sends an API key |
| `ItemShopClient` | the full keyed API + Sites API + AI Workspace | API key (`X-API-Key`) |

Every method has a blocking and a non-blocking variant (`…Async` → `CompletableFuture`).

## Installation

```kotlin build.gradle.kts theme={null}
dependencies {
    implementation("pl.itemshop:sdk:0.1.0")
    // Kotlin: implementation("pl.itemshop:sdk-kotlin:0.1.0")
}
```

## Paper plugin — asynchronous

HTTP requests run on `HttpClient` threads; return to the server's main thread with the Bukkit scheduler. **Never** call blocking methods (`list()`, `create()`…) on the main thread — use `…Async`.

```java theme={null}
public final class ShopPlugin extends JavaPlugin {
    private Storefront storefront;

    @Override
    public void onEnable() {
        storefront = Storefront.builder()
                .shop(getConfig().getString("shop-id"))   // id, uniqueName or shop domain
                .locale("pl")
                .userAgent(getName() + "/" + getPluginMeta().getVersion())
                .build();
    }

    void showProducts(Player player) {
        storefront.products().listAsync()
                .thenAccept(list -> Bukkit.getScheduler().runTask(this, () -> {
                    for (Product p : list.products()) {
                        player.sendMessage(p.name() + " — " + p.price() + " " + p.currency());
                    }
                }))
                .exceptionally(err -> {
                    Throwable cause = err.getCause() != null ? err.getCause() : err;
                    getLogger().warning("ItemShop: " + cause);
                    return null;
                });
    }

    void redeem(Player player, String code, String turnstileToken) {
        storefront.vouchers().redeemAsync(code, player.getName(), turnstileToken, null)
                .thenAccept(r -> Bukkit.getScheduler().runTask(this,
                        () -> player.sendMessage("Redeemed: " + r.productName())));
    }
}
```

Checkout from in-game (e.g. a link in a GUI):

```java theme={null}
storefront.checkout().createAsync(CheckoutInput.builder()
                .item(productId, 1)
                .nick(player.getName())
                .acceptTerms(true)            // terms — consent collected from the player
                .immediateDelivery(true)      // instant delivery + loss of the withdrawal right
                .paymentMethod(method.code(), method.channel())
                .build())
        .thenAccept(res -> Bukkit.getScheduler().runTask(this,
                () -> player.sendMessage("Pay here: " + res.redirectUrl())));
```

`checkout().create…` fills in `consents.legalVersion` from the shop profile and always sends an `Idempotency-Key`, so a retry after a dropped connection never creates a second order. A cart takes 1–20 lines (repeat `.item(...)`); two or more form one payment group. After the gateway, read the order with `Storefront.returnedOrderFromReturnUrl(url)` (`?itemshop_order=<number>&itemshop_key=<key>` → `ReturnedOrder(number, key)`), wait for payment with `orders().awaitPaymentByNumberAsync(number, key, Duration.ofSeconds(3))`, and collect digital content with `orders().deliveryAsync(number, key)`. In Kotlin: `statusByNumber`, `delivery` and `awaitPaymentByNumber` are `suspend`. The id-based `orderIdFromReturnUrl` / `awaitPaymentAsync` serve pre-3.0 orders only.

### Shadow and relocation

```kotlin theme={null}
tasks.shadowJar {
    relocate("pl.itemshop.sdk", "com.example.myplugin.libs.itemshop")
    relocate("com.google.gson", "com.example.myplugin.libs.gson")
}
```

`HttpClient` threads are daemons — they don't block server shutdown. A custom `Executor` (`.executor(...)`) also receives async callbacks and retry delays.

### Kotlin (coroutines)

```kotlin theme={null}
val sf = storefront(shopId) { locale("pl") }.suspending()
val api = itemShopClient { apiKey(System.getenv("ITEMSHOP_API_KEY")) }.suspending()

scope.launch(Dispatchers.IO) {
    val list = sf.products.list()
    api.orders.listAll(shopId, OrdersService.ListParams().status("paid"))   // Flow<Order>, pages lazily
        .collect { order -> println(order.orderNumber()) }
    val res = sf.checkout.create {
        item(list.products().first().id(), 1); nick("Steve"); acceptTerms(true); immediateDelivery(true)
    }
}
```

Cancelling the coroutine cancels the HTTP request. On Paper, hop back to the main thread with `Bukkit.getScheduler().runTask(plugin) { … }`.

## API client (with a key)

```java theme={null}
ItemShopClient client = ItemShopClient.builder()
        .apiKey(System.getenv("ITEMSHOP_API_KEY"))     // null → $ITEMSHOP_API_KEY
        .build();

Product created = client.products().create(shopId, CreateProductDto.builder()
        .name("VIP").price(19.99).serverId(serverId)
        .commands(List.of("lp user {PLAYER} parent set vip"))
        .build());

for (Order o : client.orders().listAll(shopId, new OrdersService.ListParams().status("paid"))) { /* lazy */ }
client.orders().list(shopId).autoPagingStream().limit(50).forEach(o -> { });

client.site().deploy(shopId, Files.readAllBytes(zip), SiteDeployment.PRODUCTION);   // = `itemshop deploy`
AgentSetup setup = client.workspace().agentSetup(shopId, "claude-code");             // MCP config
```

Generated services: `shops, servers, products, paymentMethods, paymentErrors, blacklist, discounts, vouchers, stats, uploads, orders`. Hand-written: `site()` (files, draft, revisions, upload, builds, deployments, diff), `templates()`, `workspace()`, `agents()`, `skills()`. Workspace tokens need a panel session — with an API key you get 401/403. For an endpoint without a typed method: `client.request("GET", "/api/v2/…", null)`. Models (`pl.itemshop.sdk.model.*`) are records with builders; `null` is not sent.

## Errors

Every failure is an unchecked `ItemShopException` with `code()` (the backend's code, e.g. `LEGAL_CONSENT_REQUIRED`, or an SDK code: `BAD_REQUEST`, `UNAUTHORIZED`, `FORBIDDEN`, `NOT_FOUND`, `CONFLICT`, `VALIDATION_FAILED`, `RATE_LIMITED`, `SERVER_ERROR`, `NETWORK_ERROR`, `TIMEOUT`, `ABORTED`…), `status()`, `details()`, `retryAfter()` and `body()`. Subclasses to catch: `BadRequestException` (400/413/422), `AuthenticationException` (401), `PermissionDeniedException` (403), `NotFoundException` (404), `ConflictException` (409), `RateLimitException` (429), `ServerException` (5xx), `ApiConnectionException` (network, timeout, cancellation). In a `CompletableFuture` the error arrives as the cause of a `CompletionException`.

## Retries, timeouts, idempotency

2 retries, backoff 300 ms·2ⁿ (max 5 s) with jitter, `Retry-After` replaces the delay. GET/HEAD and requests carrying an `Idempotency-Key` (`orders().create`, `checkout().create`) are retried on network errors, timeouts and 408/425/429/500/502/503/504; others only on 429. `timeout(...)` limits a single attempt (30 s default). Interrupting the thread waiting for the result cancels the request (`ABORTED`).

## Webhooks

The backend sends no signed webhooks to shop owners (notifications go to Discord — see [Webhooks](https://andcze.mintlify.site/en/developers/webhooks.md)), so the SDK has no signature verifier. For in-game delivery use the [plugin protocol](https://andcze.mintlify.site/en/developers/plugin-protocol.md).

> **[Storefront API](https://andcze.mintlify.site/en/developers/storefront-api.md)**
>
> The endpoints under the SDK.

> **[Plugin protocol](https://andcze.mintlify.site/en/developers/plugin-protocol.md)**
>
> Real-time order delivery.


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