inklet docs

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 capabilityFreeProUses inklet AI
Read Displays, Contents, Analyses, Presentations, queues, and current framesYesYesNo
Upload ContentYesYesNo
Switch what a display shows (setCurrent, advance)YesYesNo
analyses.direct(), Hardcode Push, and presentations.generate({ mode: "hardcode" })YesYesNo
analyses.analyze(), Auto Push, Manual Push, and presentations.generate()NoYesYes

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

LimitFreePro
Displays bound to the accountUnlimitedUnlimited
Pushes per UTC day10200
Live personal access tokens220
Display history readable7 daysUnlimited

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.

StatuscodeWhat it meansThe way out
403plan_upgrade_requiredThe plan does not include this featureUpgrade
402payment_requiredThe plan includes it, but a payment did not landFix 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.

On this page