> For the complete documentation index, see [llms.txt](https://docs.ohalee.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.ohalee.com/products/obsidian-license/downloads.md).

# Downloading the Jar

Let customers download your jar from the platform, with a valid license as the only key that opens the door.

You can hand out builds through Obsidian License instead of (or alongside) a marketplace download button. The license is the credential: a revoked or expired key stops downloading immediately, and every download lands in your access log.

This is separate from the [protected plugin](/products/obsidian-license/protected-plugins.md) flow, where the jar is delivered encrypted to a loader and never written to disk. Here the customer receives an ordinary jar file.

## Turn it on

1. **Products**, open the product, **Edit**.
2. Tick **Allow jar download**.
3. Upload a build if you have not already. The current stable version is what gets served (a license on the beta channel gets the beta build).

{% hint style="warning" %}
Leave this off for protected products. Protected products exist so the jar is never on the customer's disk in clear, and switching downloads on would hand out exactly that.
{% endhint %}

## How customers get it

### From the plugin itself

The [license-check client](/products/obsidian-license/license-check.md) has it built in, which is the neat way to write a self-updater:

```java
byte[] jar = client.download(cfg.getString("license.key"), "my-plugin");
Files.write(getDataFolder().toPath().resolve("update/MyPlugin.jar"), jar);
```

Downloading never consumes a seat, so a customer who re-downloads a build is not burning an activation.

### Directly

`POST https://license.ohalee.com/api/v1/download`, with the same body and the same `X-Signature` header as a license check:

```json
{
  "licenseKey": "OBS-XXXX-XXXX-XXXX-XXXX",
  "productId": "my-plugin",
  "nonce": "<random per request>",
  "fingerprint": {}
}
```

`X-Signature` is `base64(HMAC-SHA256(key = licenseKey, msg = the exact request body bytes))`. That proves the caller holds the key and pins the bytes, so the request cannot be replayed with a different product id.

A success returns the jar as `application/java-archive`, with the version in the `X-Plugin-Version` response header. Anything else returns the usual JSON denial:

```json
{
  "error": "license has expired",
  "reason": "denied_expired",
  "message": "This license has expired. Renew it to keep the plugin running.",
  "helpUrl": "https://license.ohalee.com/portal"
}
```

### Recovering a lost key

A buyer who lost their key does not need you. Point them at:

* `https://license.ohalee.com/redeem` to recover the key attached to their marketplace purchase.
* `https://license.ohalee.com/status` to check whether a key is active.
* `https://license.ohalee.com/portal` to see bound machines and free a seat.

{% hint style="info" %}
A marketplace key is delivered **once** from the redeem page. Marketplace user ids are public, so a key that stayed retrievable forever would be retrievable by anyone who can count. If a buyer says the page tells them the key was already redeemed and they never received it, reissue the key from your dashboard.
{% endhint %}

## Publishing a new build

Upload a new version to the product and every download from that moment serves it. Nothing to expire or invalidate, and no links to rotate.

Uploads are retained as versions, so you can promote a build to **beta** for trusted customers first and roll back instantly if it goes badly. Licenses on the stable channel keep getting the stable build throughout.

For CI, generate a personal **API key** in the dashboard and upload the freshly built jar as part of your release pipeline. See [for-developers.md](/products/obsidian-license/for-developers.md).
