LicenseOS API
A small REST API to check permission, review outputs, report usage, and validate license status. Base URL: https://licenseos.vercel.app/api/v1. Every request authenticates with a license token.
Quickstart
- Grab a key — sign up, apply for a license, then mint a token on API Keys. Keys look like
llk_live_…(orllk_test_…for sandbox). Want to try before integrating? The Playground makes a real, key-authenticated call for you and hands you the exact snippet below, pre-filled. - Drop one of these three into your app and swap
YOUR_KEYfor your token:
This first call uses noncommercial_fan — the use case your free starter grant covers — so it clears on copy-paste. Already on a commercial tier? See the commercial example below.
curl
curl -X POST https://licenseos.vercel.app/api/v1/license/check \
-H "Authorization: Bearer YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{
"ipPropertyId": "moonberry-grove",
"useCase": "noncommercial_fan",
"prompt": "A cozy birthday scene with Momo the Moon Fox",
"outputType": "image",
"distributionChannel": "web"
}'JavaScript / TypeScript (fetch, zero dependencies)
const res = await fetch("https://licenseos.vercel.app/api/v1/license/check", {
method: "POST",
headers: {
"Authorization": `Bearer ${process.env.LICENSE_OS_API_KEY}`, // YOUR_KEY
"Content-Type": "application/json",
},
body: JSON.stringify({
ipPropertyId: "moonberry-grove",
useCase: "noncommercial_fan",
prompt: "A cozy birthday scene with Momo the Moon Fox",
outputType: "image",
distributionChannel: "web",
}),
});
const decision = await res.json();
// decision.approved, decision.decision ("allow" | "warn" | "require_review" | "block"),
// decision.receipt + decision.receiptSignature ← signed proof you cleared itTyped SDK (npm i @licenseos/sdk)
import { LicenseOS } from "@licenseos/sdk";
const client = new LicenseOS({ apiKey: process.env.LICENSE_OS_API_KEY!, baseUrl: "https://licenseos.vercel.app" });
const decision = await client.checkLicense({
ipPropertyId: "moonberry-grove",
useCase: "noncommercial_fan",
prompt: "A cozy birthday scene with Momo the Moon Fox",
outputType: "image",
distributionChannel: "web",
});
if (decision.approved) { /* ship it — keep decision.receipt as proof */ }What you get back
{
"ok": true,
"approved": true,
"decision": "warn",
"riskScore": 12,
"licenseTier": "fan-noncommercial",
"requiredAttribution": "Moonberry Grove characters © Lumic Story Studio, used under license via LicenseOS.",
"usageLimits": { "monthlyGenerations": 500, "maxMonthlyRevenueCents": null },
"rationale": "Noncommercial fan use within the tier's limits.",
"auditId": "clxxxx",
"receipt": { "v": "1", "ipProperty": "moonberry-grove", "decision": "warn", "approved": true },
"receiptSignature": "losr1-es256:..." // signed, verify at https://licenseos.vercel.app/verify — no LicenseOS account needed
}Commercial example
On a commercial tier, declare commercial intent and your expected revenue. This call clears only against a grant whose tier permits commercial use — on a fan/personal grant it returns block.
const decision = await client.checkLicense({
ipPropertyId: "moonberry-grove",
useCase: "low_volume_commercial",
prompt: "A cozy birthday scene with Momo the Moon Fox",
outputType: "image",
commercialIntent: true,
expectedRevenue: 1500, // USD/month
});No login? See a real response.
GET /api/v1/decisions/sample returns a live, signed sample receipt (fictional Moonberry Grove) — no token needed. open it in your browser, or:
curl https://licenseos.vercel.app/api/v1/decisions/sample
// -> { "receipt": { "v": "1", "ipProperty": "moonberry-grove", "decision": "allow", ... },
// "signature": "losr1-es256:..." } ← verify it at https://licenseos.vercel.app/verifyAuthentication
Pass your token as a Bearer token (or the x-license-token header). Tokens are scoped to a single license (one IP property + tier). Never expose tokens in client-side code. Hashed at rest; shown once at creation.
Authorization: Bearer llk_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxLicense check (preflight)
/api/v1/license/checkAsk whether a proposed use is allowed. Returns a decision: allow warn require_review block.
Request body
{
"ipPropertyId": "moonberry-grove", // required — the IP slug
"useCase": "low_volume_commercial", // required
"prompt": "string", // optional, the generation prompt
"outputType": "image", // text|image|video|audio|app_screen|mixed
"distributionChannel": "ios_app",
"commercialIntent": true,
"expectedMonthlyUsers": 500,
"expectedRevenue": 1500, // USD/month
"territory": "US",
"ageRating": "G",
"hasUserGeneratedContent": false
}Response
{
"ok": true,
"approved": true,
"decision": "warn",
"riskScore": 12,
"humanReviewRequired": false,
"licenseTier": "low-volume-commercial",
"requiredAttribution": "© Studio, used under license via LicenseOS.",
"requiredChanges": [],
"usageLimits": { "monthlyGenerations": 5000, "maxMonthlyRevenueCents": 500000 },
"applicableRules": [ { "id": "commercial.allowed", "passed": true } ],
"violatedRules": [],
"flags": [],
"rationale": "Commercial use within the tier's limits.",
"auditId": "clxxxx", // the decision's id — keep it to link postflight + verify
"policyVersion": 1,
"aiModelVersion": "lo-compliance-1",
"receipt": { "v": "1", "ipProperty": "moonberry-grove", "decision": "warn" },
"receiptSignature": "losr1-es256:..." // ES256-signed, verifiable at /verify (older keys use losr1:)
}Output review (postflight)
/api/v1/output/reviewAfter you generate, review the actual output. Same decision shape, with its own auditId + receipt. Keep the preflight auditId in your own records to correlate the two halves of the audit trail.
{
"ipPropertyId": "moonberry-grove",
"outputType": "image",
"promptUsed": "Momo waving hello",
"imageDescription": "Momo the Moon Fox waving on a starry background",
"fileUrl": "https://...", // optional
"textContent": "string", // optional
"appContext": "Birthday card app" // optional
}Usage reporting
/api/v1/usage/reportReport generation/sale/distribution events. Drives quotas, royalties, and threshold enforcement.
{
"ipPropertyId": "moonberry-grove",
"eventType": "generation", // one of: generation, sale, distribution
"userIdHash": "sha256(...)", // optional, privacy-preserving
"region": "US",
"revenueAmount": 4.99, // optional, USD
"generatedOutputId": "..."
}
// -> { "ok": true, "accepted": true, "currentUsage": {...}, "limitsRemaining": {...}, "billingStatus": "ok" }License status
/api/v1/license/statusValidate a token and see what it allows. Reflects revocation/suspension/expiry immediately.
curl https://licenseos.vercel.app/api/v1/license/status -H "Authorization: Bearer llk_live_..."
// -> { "ok": true, "active": true, "grantStatus": "ACTIVE", "licenseTier": "...", "allows": {...}, "usageLimits": {...}, "expiresAt": null }Verify a receipt (machine-readable)
/api/v1/decisions/verify/{id}Programmatically verify a signed decision receipt — for your legal/procurement reviewers, partners, your own CI (and insurers later). Pass a decision id (or its audit id). Returns the reconstructed receipt, a verification verdict (verified / mismatch / unstored / unverifiable), the cleared grant + tier, and the ODRL license semantics when available. Public (no token); only hashes of the request are exposed, never the prompt. Complements the human /verify page.
curl https://licenseos.vercel.app/api/v1/decisions/verify/DECISION_ID
// -> { "ok": true, "verification": "verified", "valid": true, "decision": "ALLOW",
// "ipProperty": {...}, "grant": {...}, "tier": {...}, "signature": "...",
// "signedAt": "...", "verifyUrl": "...", "receipt": {...}, "license": { "@type": "Set", ... } }Revoke a license (programmatic kill switch)
/api/v1/grants/{id}/revokeRights-holder endpoint to revoke a grant from your own abuse-response automation — the same effect as the dashboard kill switch: the grant + its active tokens flip to REVOKED atomically, an audit entry is written, and the license.revoked webhook fires. Authenticated by your rights-holder session and scoped to the IP you own; revocation takes effect on the next API call and the next asset fetch immediately.
curl -X POST https://licenseos.vercel.app/api/v1/grants/GRANT_ID/revoke \
-H "Content-Type: application/json" \
-d '{ "reason": "Abuse detected by our monitor" }'
// -> { "ok": true, "grantId": "GRANT_ID", "status": "REVOKED", "webhookPropagated": true }Webhooks
/api/v1/webhooks/registerSubscribe to license lifecycle events. Returns a signing secret once — verify it on the X-LicenseOS-Signature header.
{ "url": "https://licenseos.vercel.app/webhooks/licenseos", "events": ["license.revoked", "policy.updated"] }Events: license.revoked, license.suspended, policy.updated, application.decided, usage.flagged, violation.opened, takedown.issued.
Error codes
| HTTP | Code | Meaning |
|---|---|---|
| 401 | unauthorized | Missing or invalid license token. |
| 403 | token_revoked | Token revoked or suspended. |
| 403 | forbidden | Token not scoped for this resource. |
| 404 | not_found | Resource not found. |
| 422 | invalid_request | Request failed validation. |
| 429 | rate_limited | Rate limit exceeded. |
| 500 | server_error | Internal server error. |
Errors return { "ok": false, "error": { "code", "message", "detail" } }.
TypeScript SDK
Prefer a typed client? Two ways to use it today:
- • Drop-in single file (zero dependencies): download licenseos.ts and import it:
import { LicenseOS } from "./licenseos"; - • npm:
npm install @licenseos/sdk— the published, typed client.
Either way the client reads:
import { LicenseOS } from "@licenseos/sdk";
const client = new LicenseOS({ apiKey: process.env.LICENSE_OS_API_KEY!, baseUrl: "https://licenseos.vercel.app" });
const decision = await client.checkLicense({
ipPropertyId: "moonberry-grove",
useCase: "low_volume_commercial",
prompt: "A cozy birthday scene with Momo the Moon Fox",
outputType: "image",
commercialIntent: true,
expectedRevenue: 1500,
});
if (decision.approved) {
// ship it — decision.receipt + decision.receiptSignature prove you cleared it
} else if (decision.decision === "require_review") {
// pending the rights holder's review queue
} else {
// show the restriction
}
// after you generate, review the actual output (note: reviewOutput, not "postflight"):
const review = await client.reviewOutput({ ipPropertyId: "moonberry-grove", outputType: "image", promptUsed: "..." });
await client.reportUsage({ ipPropertyId: "moonberry-grove", eventType: "generation", revenueAmount: 4.99 });
const status = await client.getStatus();