inklet docs

Analyses

The Analysis resource — run the inklet agent over uploaded Content and the user's history, target a display or a viewport, and wait for the result.

An Analysis is the unit of work. A Content is only the material you submitted; nothing is read, laid out, or rendered until an Analysis references it.

const { content } = await inklet.contents.upload({
  assets: [inklet.assets.text("Dentist at 9am tomorrow")],
});

const analysis = await inklet.analyze({
  contentIds: [content.id],
  intent: "Make a reminder card",
});

const done = await inklet.analyses.wait(analysis);
console.log(done.outcome, done.presentationIds);

inklet.analyze() and inklet.direct() are shorthands for inklet.analyses.analyze() and inklet.analyses.direct().

analyze()

analyze(input?: AnalyzeInput): Promise<Analysis>
interface AnalyzeInput {
  /** Omit or leave empty to analyze recent history without new Content. */
  contentIds?: readonly string[];
  /** Defaults to `submitted` when `contentIds` is given, else `history`. */
  context?: AnalysisContext;
  /** Only meaningful with `context: "history"`. */
  scope?: AnalysisScopeInput;
  intent?: string | null;
  title?: string | null;
  /** Omit to let the agent choose Displays. */
  target?: AnalysisTargetInput;
  idempotencyKey?: string;
}
OptionWhat it does
contentIdsThe Contents to analyze. Naming them rules out no_change: every Content you name appears in at least one Presentation, or the Analysis fails with no_presentable_content.
contextsubmitted — only the Contents you named. history — the agent may also retrieve the user's earlier Contents.
scope{ since: "72h" }, the look-back window for history. A relative duration matching /^\d+[mhd]$/.
targetOmit to let the agent choose Displays, { displayId } or { displayIds } to pin them, { output } for a display-free Scene or PNG.
intentA sentence of direction for the agent.
titleOverrides the title inklet would generate.
idempotencyKeyGenerated when omitted. See Idempotency.

context defaults to submitted when contentIds is non-empty and to history when it is empty. context: "submitted" with no contentIds throws ConfigurationError, and so does scope on anything but history.

direct()

One uploaded image shown as sent, with no AI:

direct(input: DirectInput): Promise<Analysis>
interface DirectInput {
  /** A Content holding exactly one PNG or JPEG image. */
  contentId: string;
  target: AnalysisTargetInput;
  idempotencyKey?: string;
}

A direct Analysis requires exactly one contentId, context: "submitted", and a target. inklet scales the image to the target geometry server-side, which is what Hardcode Push uses underneath.

create()

create(input: CreateAnalysisRequest, idempotencyKey: string): Promise<Analysis>

The same request with mode explicit and the key required — analyze() and direct() are wrappers over it that generate a key when you do not supply one.

Targets

type AnalysisTargetInput =
  | { displayId: string }
  | { displayIds: readonly string[] }
  | { output: PresentationOutputRequest };

Exactly one of the three keys, or ConfigurationError. Omitting target entirely is the fourth option, and the one that lets the agent route for you.

An { output } target produces a display-free Presentation — Scene JSON and PNG renditions, no panel involved. See Generating without a display.

A targetless Analysis needs at least one display it can actually use. When the account has none that fits, POST /analyses fails immediately with 422 and code: "no_compatible_display", which the SDK surfaces as an ApiError. Give a target — including an { output } target — when there may be no panel.

retrieve() and list()

const analysis = await inklet.analyses.retrieve("analysis_123");

const page = await inklet.analyses.list({
  state: "completed",
  trigger: "scheduled",
  limit: 20,
});
interface ListAnalysesOptions {
  /** Only Analyses that listed this Content in contentIds (role "input"). */
  contentId?: string;
  state?: AnalysisState;
  trigger?: AnalysisTrigger;
  cursor?: string;
  limit?: number;
}

trigger is api for the ones you started and scheduled for the ones inklet runs on the user's behalf. An out-of-range state, trigger, or limit throws ConfigurationError locally.

wait()

wait(
  analysisOrId: Analysis | string,
  options?: WaitForAnalysisOptions,
): Promise<Analysis>

Polls until the Analysis is completed and returns it. A failed Analysis throws AnalysisFailedError, with the backend's own code on error.details.backendCode.

OptionDefaultRange
pollIntervalMs1000100–60,000
timeoutMs1200001–1,800,000
signalAborting throws OperationAbortedError

The 120-second default suits a submitted Analysis. Analyses with context: "history" run one at a time per user, and scheduled ones queue behind them, so raise timeoutMs for those. A timeout throws OperationTimeoutError but does not cancel the Analysis — analyses.retrieve() still returns it once it finishes.

The Analysis type

interface Analysis {
  id: string;
  mode: "ai" | "direct";
  trigger: "api" | "scheduled";
  state: "queued" | "running" | "completed" | "failed";
  outcome: "presentations" | "no_change" | null;
  noChangeReason: string | null;
  contentIds: readonly string[];
  context: "submitted" | "history";
  scope: AnalysisScope | null;
  intent: string | null;
  title: string | null;
  /** `null` means the agent chose (or will choose) the Displays. */
  target: AnalysisTarget | null;
  presentationIds: readonly string[];
  failure: PresentationProblem | null;
  createdAt: string;
  updatedAt: string;
}

States

queued ──→ running ──→ completed
                   └─→ failed

queued and running are not terminal; completed and failed are. See Lifecycle.

Outcome

outcome is null until the Analysis finishes, then one of two answers.

OutcomeMeaning
presentationspresentationIds is populated.
no_changeThe agent looked and decided nothing was worth showing. noChangeReason says why.

no_change is a normal completion, not a failure, and it is only reachable when the Analysis named no contentIds. Name a Content and inklet either covers it or fails the Analysis with no_presentable_content.

Scope and the history window

interface AnalysisScope {
  /** What you asked for: a relative duration such as "24h", "7d", "90m". */
  since: string;
  /** The absolute start the backend resolved at creation time, or null. */
  sinceAt: string | null;
}

since is resolved to an absolute sinceAt when the Analysis is created, so an Analysis that sits in queued still reads the window it was created with. The plan caps how far back history reaches, and an earlier since is clamped to that floor rather than rejected — compare scope.sinceAt against what you asked for when you need to tell a user how much was really covered. See Plans.

Following a run

An Analysis publishes an ordered event stream while it works. It has its own page:

archive()

const { url, expiresAt } = await inklet.analyses.archive("analysis_123");

A short-lived download URL for the run archive. Throws NotFoundError with code: "archive_not_found" when there is none — while the Analysis is still running, or after retention has expired.

On this page