Puras JS/TS SDK Reference

Auto-generated reference for the puras npm package — call your deployed skills from Node, React, and React Native / Expo.

The puras npm package is the JavaScript counterpart of the Python client: it submits jobs to your deployed skills and hands back the result. Zero dependencies, one fetch-based client that runs unchanged in Node (≥18), browsers/React, and React Native / Expo.

bash
npm install puras

It is the client half only — the in-skill runtime (puras.media, puras.drive, …) and the deploy CLI ship with the Python package (pip install puras).

Address a skill the same way you see it on its page — a workspace/skillpack/skill path you can copy straight from the playground:

ts
import { Puras } from "puras";

const puras = new Puras(); // PURAS_API_KEY from env (Node)
const ad = await puras.run("acme/ugc-ads/ugc-ad", {
  product: "https://example.com/product",
  duration: 15,
});
console.log(ad.video); // signed media URL

For your own skills, set a default skillpack once and call skills bare:

ts
const puras = new Puras({ apiKey, skillpack: "ugc-ads" });
const ad = await puras.run("ugc-ad", { product: url });

Auth is a workspace API key. In a backend, use a secret key (puras_live_…). In a mobile/web app, use a publishable key (puras_pub_…) — it can only submit jobs and read the jobs it submitted, and can be fenced to specific skills and a daily spend cap when minted in the dashboard (see API keys). Never ship a puras_live_ key inside an app bundle.

Puras

Call deployed skills. Three ways to consume a job, by increasing involvement:

  • one awaitrun() submits and polls to completion, returning the skill's result (or submit(…, { wait: true }) for short jobs, which holds the HTTP request open server-side instead);
  • asyncsubmit() returns the job id immediately; check back later with get() / wait();
  • live events (SSE)events() yields the job's event stream (step, tool_call, log, …) as it happens, for progress UIs.

Pass a fully-qualified workspace/skillpack/skill path to run/submit (copyable from a skill's page), or set a default skillpack once — a slug or UUID — and call skills by bare name.

Client is exported as an alias of Puras, matching the Python SDK's puras.Client.

new Puras(options?)

ts
export interface PurasOptions {
  /** Workspace API key (`puras_live_…` or `puras_pub_…`). Falls back to the
   * `PURAS_API_KEY` env var when running under Node. */
  apiKey?: string;
  /** Default skillpack (slug or UUID) for bare skill names. */
  skillpack?: string;
  /** API origin; defaults to https://api.puras.co (or `PURAS_API_BASE`). */
  apiBase?: string;
  /** Per-request timeout in ms for non-streaming calls. Default 120 000. */
  timeoutMs?: number;
  /** Custom fetch implementation (tests, polyfills). Defaults to global fetch. */
  fetch?: typeof fetch;
}

puras.submit(skill, inputs?, options?)

ts
async submit(
    skill: string,
    inputs: Record<string, unknown> = {},
    options: SubmitOptions = {},
  ): Promise<Job>

Submit a job and return the job object ({id, status, …}) immediately — or, with wait: true, after it finishes (server-side wait, bounded by timeoutSeconds).

skill may be a fully-qualified path — "workspace/skillpack/skill" (copyable from the skill's page) or "skillpack/skill" for one of your own — in which case the skillpack is taken from the path. A bare skill name uses the default skillpack passed here or to new Puras(...).

puras.run(skill, inputs?, options?)

ts
async run(
    skill: string,
    inputs: Record<string, unknown> = {},
    options: RunOptions = {},
  ): Promise<Record<string, unknown>>

Submit + wait, returning the skill's result object. Throws JobError (carrying the full job) if the job failed, was cancelled, or is still running when timeoutMs (default 10 minutes) elapses.

Waiting is client-side polling (pollIntervalMs, default 2 s) rather than a held connection — media skills run for minutes, longer than any proxy keeps a request open, and React Native's fetch can't stream.

puras.get(jobId)

ts
async get(jobId: string): Promise<Job>

Fetch a job by id (status, result, error, outputs with signed URLs).

puras.wait(jobId, options?)

ts
async wait(jobId: string, options: WaitOptions = {}): Promise<Job>

Poll a job until it reaches a terminal status (succeeded / failed / cancelled) or timeoutMs elapses; returns the last-seen job either way.

puras.events(jobId, options?)

ts
async *events(jobId: string, options: EventsOptions = {}): AsyncGenerator<JobEvent>

Stream a job's events live — an async iterator that yields each JobEvent as the worker emits it and ends when the job reaches a terminal status:

ts
const job = await puras.submit("acme/ugc-ads/ugc-ad", { product: url });
for await (const ev of puras.events(job.id)) {
  console.log(ev.type, ev.payload);   // step, tool_call, log, …
}
const done = await puras.get(job.id); // terminal by now

Transport is picked automatically: Server-Sent Events over a streaming fetch (GET /v1/jobs/{id}/stream) where the runtime supports it (Node ≥18, browsers), falling back to polling GET /v1/jobs/{id}/events on runtimes that can't stream (React Native / Expo). Force one with transport: "sse" | "poll". The stream replays the job's full event backlog first, so a viewer attached late still sees everything (resume with afterId).

Types

ts
/** A job as returned by the API. Extra fields ride along untyped. */
export interface Job {
  id: string;
  status: string;
  result?: Record<string, unknown> | null;
  error?: string | null;
  skill_name?: string;
  created_at?: string;
  [key: string]: unknown;
}
ts
/** One job event (`step`, `tool_call`, `log`, …) as emitted by the worker. */
export interface JobEvent {
  id: number;
  type: string;
  payload: Record<string, unknown> | null;
  [key: string]: unknown;
}
ts
export interface SubmitOptions {
  /** Skillpack override (slug, `workspace/skillpack` path, or UUID). */
  skillpack?: string;
  /** Pin the run to a specific deployment version; omit for the active one. */
  version?: number;
  /** Block server-side until the job finishes or `timeoutSeconds` elapses
   * (synchronous mode — the HTTP request itself waits). Short jobs only;
   * for multi-minute media jobs prefer `run()` or `wait()`. */
  wait?: boolean;
  /** Server-side wait budget in seconds (only with `wait: true`). Default 60. */
  timeoutSeconds?: number;
}
ts
export interface WaitOptions {
  /** Give up after this long. Default 600 000 (10 min — media jobs take minutes). */
  timeoutMs?: number;
  /** Poll interval. Default 2000. */
  pollIntervalMs?: number;
}
ts
export interface EventsOptions {
  /** Yield only events with id greater than this (resume a dropped stream). */
  afterId?: number;
  /** `"sse"` = live Server-Sent Events over a streaming fetch; `"poll"` =
   * repeated `GET /v1/jobs/{id}/events` (works everywhere, incl. React
   * Native, whose fetch can't stream). Default `"auto"`: SSE when the
   * runtime supports response streaming, else poll. */
  transport?: "auto" | "sse" | "poll";
  /** Poll interval for the poll transport. Default 1500. */
  pollIntervalMs?: number;
  /** Abort the stream early (e.g. when a screen unmounts). */
  signal?: AbortSignal;
}
ts
export type RunOptions = Pick<SubmitOptions, "skillpack" | "version"> & WaitOptions;

Errors

PurasAPIError

Non-2xx response from the Puras API. Notable cases: 402 insufficient credits, 403 skill not in the key's allowlist, 429 the key's daily spend cap is reached.

JobError

A job finished in a non-succeeded state (failed / cancelled / still running at timeout). .job holds the full job object.