License Server API
The license server is a small, separate FastAPI service from the main Agon backend — it is the only Agon component that knows about paying customers, Stripe subscriptions, and license keys. It never sees studio or client data; that stays local to each studio's own installation. It is not covered by docs-site/scripts/fetch-openapi.js (which only pulls from the main backend's OpenAPI spec at http://localhost:8000/openapi.json), so this page is maintained by hand.
This page is for developers integrating with or extending Agon's licensing and hosted-AI infrastructure — not for studio managers. If you're looking for how to connect your own AI provider key from the desktop app, see AI Assistant Setup instead; the endpoints below describe a different, service-to-service path (see Hosted AI vs. bring-your-own-key).
Base URL
The license server runs as its own deployment, independent of a studio's local Agon installation:
https://license.agon-studio.dev
Authentication
Two different credentials are used across this service's endpoints:
- License key (
Authorization: Bearer {license_key}) — used by the AI proxy endpoints below. This is the samelicense_keya studio's backend already holds after checkout, and the same trust-on-first-use pattern used byGET /license/verify: the key itself is the credential, looked up by exact match, never logged. - Stripe Checkout Session id (
session_idquery parameter) — used only byGET /checkout/confirm. Session ids are Stripe-generated and already unguessable, so no separate secret is needed.
For the license-key credential, an unknown key and a key belonging to an inactive license are deliberately indistinguishable: both return the same generic 401 AI_UNAUTHORIZED. Do not build integration logic that assumes you can tell these two cases apart from the response.
Hosted AI vs. bring-your-own-key
Agon's AI features can be powered two different ways:
- Bring-your-own-key — a studio manager connects their own API key for a supported provider (Google Gemini or Groq) from the desktop app's Settings → AI Assistant "Advanced settings" (see AI Assistant Setup). Requests go straight from the studio's own backend to the studio's own provider account.
- Hosted AI proxy (this page) — a studio's backend reaches Agon's AI Agent feature through the license server instead, without ever holding an LLM API key itself. Agon buys tokens centrally from Groq and resells access, metered against a hard per-license monthly cap.
The two endpoints documented below (POST /ai/completion and POST /ai/chat/completions) are the hosted-AI path.
POST /ai/chat/completions
POST /ai/completion
Both paths are registered on the exact same handler — identical auth, metering, and cap logic. In practice, only /ai/chat/completions is reached: the studio-side backend calls this proxy through litellm.completion(api_base=...), and litellm's OpenAI-compatible client always POSTs to {api_base}/chat/completions — that suffix is hardcoded in litellm and not configurable. /ai/completion is kept available as a direct-call path for any caller not going through litellm.
Request and response bodies mirror Groq's own OpenAI-compatible chat-completions endpoint (POST https://api.groq.com/openai/v1/chat/completions) exactly. The caller's body is forwarded as-is, with two exceptions:
modelis ignored and overridden server-side to Agon's pinned model. A caller cannot use this proxy to reach a different or pricier model.streamis ignored and forced tofalse. This proxy does not support streaming responses (no SSE passthrough).
Because the shapes match, a caller's existing OpenAI-compatible / litellm client needs no custom parsing to use this endpoint.
Headers
| Name | Required | Description |
|---|---|---|
Authorization | Yes | Bearer {license_key} |
Request body
| Field | Type | Required | Notes |
|---|---|---|---|
messages | array | Yes | Non-empty. Capped at 50 messages per request. |
| ...other fields | — | No | Forwarded to Groq as-is (e.g. temperature, max_tokens), except model and stream — see above. |
Responses
- 200 — The provider's JSON response, returned verbatim (same shape as Groq's chat-completions response, including its
usageobject). - 401
AI_UNAUTHORIZED— Missing/malformedAuthorizationheader, unknown license key, or a license that is notactive. These cases are indistinguishable by design. - 422
AI_INVALID_REQUEST—messagesis missing, not a list, empty, or exceeds 50 entries. - 429
AI_QUOTA_EXCEEDED— This license has already reached (or would exceed) its monthly token cap. The request is rejected before it reaches Groq — the cap protects spend, it isn't just a post-hoc usage log. - 502
AI_PROVIDER_UNAVAILABLE— Network failure reaching Groq. - 502
AI_PROVIDER_ERROR— Groq returned an error status. The provider's raw error body is never forwarded (it could echo request content).
Metering
Token usage is tracked per license (License.ai_tokens_used), incremented only from the real provider response's usage.total_tokens after a successful call — never estimated ahead of time. The counter resets once per billing period; a request landing after the license's current_period_end has passed starts a fresh period automatically.
The cap itself is a fixed number of tokens per license per month (currently 2,000,000, sized for typical natural-language agent-command usage, not high-volume chat). Once a license is at or over the cap, every further request gets 429 AI_QUOTA_EXCEEDED until the next billing period resets the counter.
GET /checkout/confirm
Delivers a license's key and related post-purchase links to a checkout success page, once.
GET /checkout/confirm?session_id={stripe_checkout_session_id}
session_id is the Stripe Checkout Session id from the just-completed purchase — it acts as the credential for this endpoint (see Authentication above). The endpoint verifies the session against the Stripe API before touching Agon's own database.
The response is only returned successfully once per session. The first successful call marks the underlying license as redeemed; every call after that — and any call with an unknown or not-yet-processed session id — returns the same generic 404 SESSION_NOT_FOUND. There is currently no way to re-display the key to a customer who closes the success page before copying it (no email-delivery pipeline exists yet for this).
Query parameters
| Name | Required | Description |
|---|---|---|
session_id | Yes | The Stripe Checkout Session id. |
Responses
-
200
{"license_key": "…","plan": "base","download_urls": {"mac": "https://dl.agon-studio.dev/0.1.0/Agon-Setup-mac.dmg","windows": "https://dl.agon-studio.dev/0.1.0/Agon-Setup-win.exe","linux": "https://dl.agon-studio.dev/0.1.0/Agon-Setup-linux.AppImage"},"manage_subscription_url": "https://billing.stripe.com/…"}license_key/plan— as before.download_urls— platform-specific installer URLs for the current Agon desktop release (mac/windows/linux), built from the service's configured download host and current version. Always present.manage_subscription_url— a Stripe Billing Portal session URL the customer can use to manage their subscription (update payment method, cancel, etc.). Can benull. Integrators must handle the null case: the portal session is only created if the license has a Stripe customer id on file, and creation can independently fail (e.g. a transient Stripe error). Either condition degrades tonullrather than failing the whole request — the license key and download links are the critical payload and are never blocked on the billing portal succeeding.
-
404
SESSION_NOT_FOUND— Unknown session id, a session not yet processed by the Stripe webhook, or a session already redeemed. These cases are intentionally indistinguishable.
Related pages
- AI Assistant Setup — the bring-your-own-key path for studio managers
- AI Action Mode
- Stripe-billing endpoints — the main backend's own Stripe integration (subscriptions for memberships, distinct from license-server's subscription for the Agon license itself)