inklet docs

Presentations

The Presentation resource — a rendered frame for a display or a display-free Scene, with history filtering, renditions, and the plan-capped history window.

A Presentation is one finished result of an Analysis. It comes in two kinds, and kind is derived from whether it names a display:

KinddisplayIdWhat it is
displaysetA frame for one panel, delivered through that display's queue.
generatednullA display-free result — Scene JSON and PNG renditions, no panel involved.

retrieve()

const presentation = await inklet.presentations.retrieve("presentation_123");
retrieve(
  presentationId: string,
  options?: { format?: "png" | "raw2" | "raw4" },
): Promise<Presentation>

format selects a rendition on the legacy image field and only applies to display Presentations. A format other than the three throws ConfigurationError. Generated Presentations return every rendition they have, in renditions.

list()

const page = await inklet.presentations.list({
  displayId: display.id,
  state: "expired",
  limit: 20,
});

Prop

Type

Without a displayId, scope defaults to generated, so a bare presentations.list() returns display-free Presentations and none of the display ones. Ask for scope: "display" or scope: "all" when you want frames that went to a panel.

Filtering by display

displayId is how you read one panel's history. displays.listQueue() covers only what has not been shown yet; this also returns the published, confirmed, and expired ones.

// Everything this panel has shown or is about to show.
const history = await inklet.presentations.list({ displayId: display.id });

// What it is showing now.
const live = await inklet.presentations.list({
  displayId: display.id,
  state: "published",
});

scope is optional alongside displayId and defaults to display, since that is the only scope a display filter can mean. scope: "all" narrows to the display half just the same.

scope: "generated" contradicts a displayId filter and is rejected with 400 and code: "invalid_request". A display id that is not well formed is rejected the same way; one that simply does not exist, or belongs to someone else, returns an empty page rather than an error.

The history window

Prop

Type

The plan caps how much display history a list may reach. The backend clamps instead of refusing: Presentations created before the floor are left out of items, and paging past it ends the list.

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()}`);
}

Read it as the floor of what you are showing, not as an error. Nothing is deleted — after an upgrade the same call returns the older rows untouched.

historyWindowStart is reported whenever a floor applies, not only when rows were actually left out — so it is safe to render as "history starts here" on a page that happens to be short. It is null when no floor applies: an unlimited plan, or a read with no display half (scope: "generated"). Only the display half is capped, so scope: "display" and scope: "all" can report a floor. displays.listQueue(), displays.current(), and presentations.retrieve() are unaffected: a Presentation whose id you already hold stays readable either way. See Plans.

The Presentation type

Prop

Type

States

The two kinds use two vocabularies, because most of the words are facts about a panel and a generated Presentation has no panel. PresentationState is the union of both.

A generated Presentation is preparing until its Scene and every rendition it was created with are durable, then ready:

preparing ──→ ready
          └─→ failed

A display Presentation reports where its delivery has got to:

preparing ──→ queued ──→ published ──→ confirmed
                                   └─→ expired
                                   └─→ failed
StateKindMeaning
preparingbothThe render is still being produced.
readygeneratedThe Scene and its renditions are durable.
queueddisplayWaiting for the display to ask for work.
publisheddisplayHanded to the display.
confirmeddisplayThe display reported it is showing this frame.
expireddisplaySuperseded, or replaced on the panel.
failedbothRendering or delivery failed; see failure.

An expired Presentation can be put back on the panel with displays.setCurrent() — that is how "go back to the previous image" works.

title

What the Presentation is called in a display's history. inklet resolves it when the plan is accepted, in this order: the plan's own title, else the template's title parameter, else the first input Content's title or an excerpt of its text, else the Analysis title or the template name. It is null only for Presentations stored before titles were resolved at acceptance.

Which Contents are behind it

Prop

Type

input means the caller named the Content in the Analysis contentIds; context means the agent retrieved it from the user's history and used it. The backend orders them input first, in the order the Analysis named them, then the context refs. A Content appears at most once.

Renditions and images

Prop

Type

Each rendition has its own lifecycle, independent of the Presentation's state. preparing means the render has not landed yet — read the Presentation again until it settles. failed means this one geometry could not be produced; failure says why, the Scene and sibling renditions are unaffected, and asking for the same geometry again returns the same rendition rather than retrying. url and expiresAt are null whenever there is no PNG to link to, so branch on url, never on state alone.

Prop

Type

Every URL here is temporary — expiresAt says when it stops working. Download the bytes if you need to keep them; do not store the URL.

image is the legacy single-image view, kept for display Presentations and the format query. New code should read renditions.

Failures

Prop

Type

retryable tells you whether running the same Analysis again is worth trying. assetIndex points at the specific asset that caused it, when one did.

if (presentation.state === "failed" && presentation.failure) {
  const { code, message, retryable, assetIndex } = presentation.failure;

  console.error(`${code}: ${message}`);
  if (assetIndex !== null) console.error(`asset ${assetIndex} is at fault`);
  if (retryable) await retryLater();
}

The same shape appears on Content.failure and Analysis.failure.

Generating without a display

generate() is contents.upload() followed by an Analysis with an { output } target. It registers no display, publishes no queue entry, and sends nothing to a panel.

const generation = await inklet.presentations.generate({
  intent: "Create a calm, glanceable summary",
  assets: [inklet.assets.text("Revenue increased 12% this week.")],
  output: { viewport: { width: 360, height: 170 }, formats: ["scene", "png"] },
});

const presentation = await inklet.presentations.waitUntilReady(generation);

console.log(presentation.scene?.data);
console.log(presentation.renditions[0]?.url);

Prop

Type

preset and viewport are mutually exclusive, and formats defaults to both. Viewport dimensions are integers between 1 and 8192. Passing mode: "hardcode" with a single image instead of assets sends one PNG or JPEG through without AI.

An { output } target always produces exactly one Presentation, so waitUntilReady() returns it. It throws NoChangeError only when it is handed an Analysis that named no contentIds and completed as no_change, and MultiplePresentationsError — with presentationIds — when it is handed one that produced several, such as an Analysis pinned to several displays. Use analyses.wait() for those.

waitUntilReady() waits for the Analysis, which settles once the Scene is stored; the pixels can still be rendering, so check a rendition's url before using it.

preset names a size. With neither preset nor viewport, inklet uses default:

presetViewport
default800 × 480 — the panel geometry
macos-widget-small170 × 170
macos-widget-medium360 × 170
macos-widget-large360 × 376

An unknown preset is refused with code: "invalid_request".

Scene JSON

A generated Presentation carries a versioned Scene alongside its PNG renditions:

import { INKLET_SCENE_MEDIA_TYPE } from "@inklethq/sdk";
// "application/vnd.inklet.scene+json;version=1"

Prop

Type

Prop

Type

A v1 renderer must handle text, image, and shape. Element-specific styling lives in properties, so inklet can add an optional style without a new SDK release. An element type added later arrives as its own string rather than failing the Presentation — skip one you cannot draw.

render()

A stored Scene can be rendered again at another size without rerunning AI:

const rendition = await inklet.presentations.render(presentation.id, {
  viewport: { width: 720, height: 340 },
});
render(
  presentationId: string,
  input: { preset?: string; viewport?: PresentationViewport; colorMode?: PresentationColorMode },
): Promise<PresentationRendition>

The output format is always png. The first call for a new geometry returns the rendition in state: "preparing" with url: null; the backend deduplicates on format, viewport, and colour mode, so calling again with the same input returns that same rendition as it stands instead of rendering twice. Poll retrieve() until the rendition's url is set:

let rendition = await inklet.presentations.render(presentation.id, {
  viewport: { width: 720, height: 340 },
});

while (rendition.state === "preparing") {
  await new Promise((resolve) => setTimeout(resolve, 2000));
  const latest = await inklet.presentations.retrieve(presentation.id);
  rendition = latest.renditions.find((r) => r.id === rendition.id) ?? rendition;
}

if (rendition.url) await download(rendition.url);
else console.error(rendition.failure?.message);

Downloading a frame

const presentation = await inklet.presentations.retrieve(id, { format: "png" });
const url = presentation.renditions[0]?.url ?? presentation.image?.url;

if (url) {
  const response = await fetch(url);
  await writeFile(`${presentation.id}.png`, Buffer.from(await response.arrayBuffer()));
}

This is a plain fetch — image URLs are pre-authorized and take no inklet credentials.

On this page