Plans and AI features
Which inklet SDK features use AI, which plan they require, the quotas each plan carries, and how far back display history reaches.
inklet authorizes every SDK request against the current plan of the user who owns the personal access token. A valid PAT proves who the caller is; the plan decides which processing features that user can run, and how much history they can read.
| SDK capability | Free | Pro | Uses inklet AI |
|---|---|---|---|
| Read Displays, Contents, Analyses, Presentations, queues, and current frames | Yes | Yes | No |
| Upload Content | Yes | Yes | No |
Switch what a display shows (setCurrent, advance) | Yes | Yes | No |
analyses.direct(), Hardcode Push, and presentations.generate({ mode: "hardcode" }) | Yes | Yes | No |
analyses.analyze(), Auto Push, Manual Push, and presentations.generate() | No | Yes | Yes |
Manual Push still requires Pro. Choosing the display yourself skips automatic
routing, but inklet still reads, plans, and lays out the assets.
analyses.direct() — which is what Hardcode Push uses — receives a finished PNG
or JPEG and only scales it to the output size, so it remains available on Free.
The gate is on the Analysis, not on the upload. contents.upload() works on
every plan; a mode: "ai" Analysis is what requires Pro, and
contents.create() cannot bypass it.
Quotas
| Limit | Free | Pro |
|---|---|---|
| Displays bound to the account | Unlimited | Unlimited |
| Pushes per UTC day | 10 | 200 |
| Live personal access tokens | 2 | 20 |
| Display history readable | 7 days | Unlimited |
Running out of a counted quota is a 429 with code: "quota_exceeded", which
the SDK raises as a RateLimitError. Its details carry quota, limit,
used, currentTier, and resetAt — the instant the window rolls over.
History depth
The plan also caps how far back the SDK can look, and it is the one quota that shapes responses rather than refusing them.
Reading a display's history.
presentations.list() clamps instead
of failing: display Presentations older than the floor are left out of items,
and the page reports the floor in historyWindowStart.
const history = await inklet.presentations.list({ displayId: display.id });
if (history.historyWindowStart) {
const since = new Date(history.historyWindowStart);
console.log(`Showing history since ${since.toLocaleDateString()}`);
}Analyzing history. An Analysis with context: "history" explores the same
window. An earlier scope.since than the plan allows is clamped at creation
rather than rejected, and the resolved absolute start comes back on
analysis.scope.sinceAt — compare it against what you asked for when you need
to tell a user how much was really covered.
Nothing is deleted. The hidden rows come back on the same call after an
upgrade. presentations.retrieve(), displays.listQueue(), and
displays.current() are not affected either: a Presentation whose id you
already hold stays readable whatever the plan says.
Authorization failures
A plan refusal is not transient. Do not retry it until the subscription changes.
| Status | code | What it means | The way out |
|---|---|---|---|
| 403 | plan_upgrade_required | The plan does not include this feature | Upgrade |
| 402 | payment_required | The plan includes it, but a payment did not land | Fix the card |
import { ApiError, PermissionDeniedError } from "@inklethq/sdk";
try {
await inklet.push.auto({ assets });
} catch (error) {
if (error instanceof PermissionDeniedError && error.code === "plan_upgrade_required") {
console.error(error.details); // { feature, requiredTier, currentTier, status }
}
if (error instanceof ApiError && error.code === "payment_required") {
// Send the customer to the billing portal, not to checkout.
}
}The two codes are deliberately different: one customer has not bought the feature, the other has and needs a working card. See Errors.
Upgrade or manage the subscription in the inklet portal. The SDK does not create checkout sessions, open the billing portal, list invoices, or expose payment-provider identifiers.
After an upgrade
You do not need to issue a new PAT. Authorization uses the token owner's current server-side plan on each request, so retry the operation with the same client once the plan change is active — and the history that was clipped is back on the next read.