# LLM Publisher Guide (/docs/llm/publisher)



Publish an AI model as a Void App. One model per app, billed per token. Your server just needs to speak OpenAI-compatible `POST /v1/chat/completions`.

## Quick start [#quick-start]

Use any OpenAI-compatible server — vLLM, Ollama, or similar:

```bash
# Ollama
ollama pull smollm2:135m
curl http://localhost:11434/v1/chat/completions -H "Content-Type: application/json" \
  -d '{"model":"smollm2:135m","messages":[{"role":"user","content":"hi"}],"stream":false}'

# vLLM
vllm serve HuggingFaceTB/SmolLM2-135M-Instruct --host 0.0.0.0 --port 9000 --served-model-name smollm2:135m
curl http://localhost:9000/v1/chat/completions -H "Content-Type: application/json" \
  -d '{"model":"smollm2:135m","messages":[{"role":"user","content":"hi"}]}'
```

Both return:

```json
{"choices":[{"message":{"content":"Hello"}}],"usage":{"prompt_tokens":10,"completion_tokens":5,"total_tokens":15}}
```

## Publish (Voidnet Console) [#publish-voidnet-console]

1. Verify a domain: **Console → Domains** → follow the verification steps.
2. Connect payouts: **Console → Payments** → connect and activate your Stripe account. **Every paid tier requires an active `stripe_transfers` capability** — publishing a paid tier without it is rejected, because earnings have no payout destination otherwise.
3. Go to **Publish Apps → LLM** and fill in:
   * **App Name** — unique per publisher `a-z, 0-9, -, _` (3–50 chars)
   * **Model** — the exact model ID your server serves (for example `HuggingFaceTB/SmolLM-135M-Instruct`)
   * **Server URL** — `https://your-host/v1` (must end with `/v1`; the gateway will call `/chat/completions` on it)
4. Click **Create Draft** — you will receive a publisher API key. The gateway uses it to authenticate to your server, so your server should accept any `Authorization: Bearer <token>` header.
5. In the overview click **Publish** to make your app live in the Marketplace.

You can configure free tiers, paid tiers, or both, with token limits and pricing during publishing.

## Your server contract [#your-server-contract]

Your server must expose a single endpoint:

**`POST {serverUrl}/chat/completions`** — `{serverUrl}` is the configured Server URL above (already ends in `/v1`).

**`GET {serverUrl}/models`** — expose your OpenAI models listing (vLLM and Ollama serve it natively). The gateway proxies it to buyers at `GET /v1-beta/llm/{username}/{appname}/models`, non-billable.\*\*

**Request — the gateway forwards the buyer's JSON verbatim:**

```json
{"model":"your-model-id","messages":[{"role":"user","content":"hi"}],"temperature":0.7,"max_tokens":64,"stream":false}
```

`model` and `messages` are required. Multimodal content (`image_url`, `audio`, `video`) is rejected before reaching your server — TEXT only.

**Streaming — supported, with one hard rule:** `stream:true` requests reach your server as SSE. Your stream **must** end with a final data chunk carrying `usage` (OpenAI `stream_options.include_usage` shape) before `[DONE]`. The gateway injects `stream_options.include_usage:true` into buyer requests that lack it, but if your server drops it and never sends usage, the call fails `502` instead of going unbilled. vLLM always sends usage; Ollama needs the option.

**Response — you must return valid OpenAI shape:**

```json
{
  "id": "chatcmpl-...",
  "object": "chat.completion",
  "created": 123,
  "model": "your-model-id",
  "choices": [{"index":0,"message":{"role":"assistant","content":"Hello"},"finish_reason":"stop"}],
  "usage": {"prompt_tokens":10,"completion_tokens":5,"total_tokens":15}
}
```

`total_tokens` must be present and greater than zero — it is the billing input. Return `4xx`/`5xx` with `{"error":{"message":"..."}}` on failure; the gateway maps your status range to `publisher_client_error`/`publisher_server_error` and preserves your body.

**Auth:** Accept any `Authorization: Bearer <token>` header.

**Limits:** Response must be under 16MB.

## Tiers and billing [#tiers-and-billing]

* Apps are metered per token (`total_tokens` from your response). Your free and paid tier limits are token limits.
* Paid LLM apps price **per 1M tokens**. Each buyer call deducts `total_tokens / 1M × price` from the buyer's wallet and credits your earnings in the same atomic step. Fixed-price tiers deduct the full price at purchase instead.
* The platform fee defaults to &#x2A;*20%** (`floor(gross × pct / 100)`).
* Buyers on a free tier who upgrade to paid are moved automatically: the free purchase is revoked when the paid grant completes. One active tier per buyer per app.

## Payouts [#payouts]

Wallet earnings accumulate in the platform ledger and pay out **weekly on Fridays**, one Stripe Transfer per publisher per week covering all your apps and sales, after a 7-day settlement hold. Track earnings, per-app breakdowns, and payout history (week, sales count, transfer id, status) in **Console → Payments**. Legacy Stripe-checkout earnings split automatically at charge time and never enter this ledger. There are no buyer-facing refunds; disputed charges are handled through Stripe.

## Test via gateway [#test-via-gateway]

After your app is published and a buyer has purchased it (free or paid):

```bash
curl -X POST "https://api.openvoidnet.com/v1-beta/llm/{publisher}/{app}" \
  -H "Authorization: Bearer vnb-sk-..." \
  -H "Content-Type: application/json" \
  -d '{"model":"your-model-id","messages":[{"role":"user","content":"hello"}]}'
```

Or with the OpenAI SDK — just change the `base_url`:

```python
from openai import OpenAI
client = OpenAI(base_url="https://api.openvoidnet.com/v1-beta/llm/{publisher}/{app}", api_key="vnb-sk-...")
response = client.chat.completions.create(model="your-model-id", messages=[{"role":"user","content":"hi"}])
print(response.choices[0].message.content, response.usage.total_tokens)
```

Streaming test — expect SSE chunks plus a final usage chunk:

```bash
curl -X POST "https://api.openvoidnet.com/v1-beta/llm/{publisher}/{app}" \
  -H "Authorization: Bearer vnb-sk-..." \
  -H "Content-Type: application/json" \
  -d '{"model":"your-model-id","messages":[{"role":"user","content":"hello"}],"stream":true}'
```

## Limits [#limits]

TEXT only, single model per app, no embeddings. `total_tokens` present and greater than zero on every success. Full interactive reference: [LLM API](https://docs.openvoidnet.com/docs/reference/api/llm). See [Buyer Guide (LLM)](https://docs.openvoidnet.com/docs/llm/buyer).
