Public pricing
/v1/public/{tenantId}/{productId}/pricing Every purchasable key type on one app, with its price, term and device cap. Public — no auth, so it can be called straight from your marketing site’s JavaScript.
It exists so you never have to hardcode a price. Write $29 into your pricing page and the day you change it in Keylight your site quotes one number while Stripe charges another. Read it from here instead and the page follows on the next load.
Request
Section titled “Request”curl https://api.keylight.dev/v1/public/acme/myapp/pricingconst res = await fetch('https://api.keylight.dev/v1/public/acme/myapp/pricing');const { keyTypes } = await res.json();Response
Section titled “Response”{ "tenantId": "acme", "product": { "productId": "myapp", "displayName": "My App" }, "keyTypes": [ { "keyTypeId": "team", "displayName": "Team", "description": "Up to 5 devices per seat · Lifetime", "priceInCents": 9900, "priceCurrency": "usd", "billingModel": "one_time", "durationDays": null, "activationLimit": 5, "entitlements": ["pro-export"], "scalesWithQuantity": true, "maxSeats": 1000, "buyUrl": "https://api.keylight.dev/buy/acme/myapp/team" } ]}| Field | Notes |
|---|---|
description | The same line the buyer reads under the product name at Stripe checkout. |
priceInCents | Per seat when scalesWithQuantity is true, otherwise the whole price. |
activationLimit | Devices per seat when scalesWithQuantity is true. |
scalesWithQuantity | When true, append ?qty=N to buyUrl to sell N seats. |
maxSeats | Upper bound qty is clamped to. 1 for a key type that does not scale. |
buyUrl | Send the buyer here. Never build the URL yourself. |
Building a pricing card
Section titled “Building a pricing card”const res = await fetch('https://api.keylight.dev/v1/public/acme/myapp/pricing');const { keyTypes } = await res.json();const team = keyTypes.find((k) => k.keyTypeId === 'team');
let seats = 1;function render() { price.textContent = `$${((team.priceInCents * seats) / 100).toFixed(2)}`; caption.textContent = team.description; buy.href = team.scalesWithQuantity ? `${team.buyUrl}?qty=${seats}` : team.buyUrl;}The seat stepper stays yours — your design, your page. Keylight supplies the numbers and takes the payment.
What it leaves out
Section titled “What it leaves out”Only what a buyer can actually purchase is listed. Hand-issued key types, and any key type with no synced Stripe price, are omitted rather than advertised — a pricing page should never show a button that fails.
Errors
Section titled “Errors”| Status | When |
|---|---|
404 | Unknown app, or nothing on it is purchasable. Also returned for a tenant that cannot currently sell — the endpoint never reports why. |
429 | Rate limited (120 requests per minute per IP). |
Responses are cacheable for 60 seconds and answer any origin. Handle a 404 by falling back to whatever your page renders when the fetch fails, rather than showing an empty pricing table.
Related
Section titled “Related”- Sell in multiples — how seat packs work
- Selling seats from your own site