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

Brand new? You don’t need to write any code to see how this works — open the in-app Playground and run a check in your browser. When you’re ready to integrate, there’s nothing to install — it’s one HTTP request.
  1. Grab a key — sign up, apply for a license, then mint a token on API Keys. Keys look like llk_live_… (or llk_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.
  2. Drop one of these three into your app and swap YOUR_KEY for 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 it

Typed 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
});
Prefer tools over copy-paste?Download OpenAPI specRun in PostmanGenerates a typed client or a ready-to-run collection — zero hand-copying.

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/verify

Authentication

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_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

License check (preflight)

POST/api/v1/license/check

Ask 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)

POST/api/v1/output/review

After 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

POST/api/v1/usage/report

Report 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

GET/api/v1/license/status

Validate 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)

GET/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)

POST/api/v1/grants/{id}/revoke

Rights-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

POST/api/v1/webhooks/register

Subscribe 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

HTTPCodeMeaning
401unauthorizedMissing or invalid license token.
403token_revokedToken revoked or suspended.
403forbiddenToken not scoped for this resource.
404not_foundResource not found.
422invalid_requestRequest failed validation.
429rate_limitedRate limit exceeded.
500server_errorInternal 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();