npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@sume-com/sdk

v0.2.0

Published

TypeScript client for the Sume HTTP API, with run polling and webhook verification helpers.

Readme

@sume-com/sdk

TypeScript client for the Sume HTTP API. Every operation in the public OpenAPI schema, plus the helpers every partner otherwise writes by hand: subscribeFormatRun, uploadFile, waitForRun, and verifyWebhook.

npm install @sume-com/sdk

Requires a runtime with fetch and WebCrypto — Node 18+, Bun, Deno, Cloudflare Workers, or a browser. The package has no runtime dependencies.

Published on npm as @sume-com/sdk, MIT licensed.

Full docs: https://docs.sume.com/sdk. This README covers the same surface plus how the package is generated, gated, and published.

Usage

import { createSumeClient, listFormats } from "@sume-com/sdk";

const client = createSumeClient({ apiKey: process.env.SUME_API_KEY! });
const { data } = await listFormats({ client });

Auth defaults to x-api-key only (scheme-aware); bearer Authorization is not set.

Server-side only. A Sume API key spends your credits. Never ship one to a browser or a mobile bundle — see Embed a Format in your product.

Wait for a run

Format, Action, and Agent Completion runs are asynchronous. waitForRun polls status_url to a terminal status and then resolves with the full receipt.

import {
  createSumeClient,
  createFormatRunByVanityPath,
  waitForRun,
} from "@sume-com/sdk";

const client = createSumeClient({ apiKey: process.env.SUME_API_KEY! });

const { data: created } = await createFormatRunByVanityPath({
  client,
  path: { handle: "acme", slug: "product-promo" },
  body: { input: { product_url: "https://shop.example.com/p/8823" } },
});

const run = await waitForRun(created!.data.id, {
  client,
  family: "format",
  timeout: 15 * 60_000,
  pollInterval: 2_000,
  signal: AbortSignal.timeout(20 * 60_000),
  onStatus: (status) => console.log(status),
});

if (run.status === "completed") console.log(run.primary_output_url);

| Option | Default | Notes | | -------------- | -------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | family | — | Required: "action" \| "format" \| "agent". A run id does not say which surface it belongs to, so it cannot be inferred. | | client | module default | The client from createSumeClient(). | | timeout | 10 min | Exceeding it throws SumeRunTimeoutError. The deadline is checked before sleeping, so a short timeout does not first wait out a poll interval. | | pollInterval | 2 s | Gap between status reads. | | signal | — | Aborts the wait and the in-flight request. Rejects with the signal's reason. | | onStatus | — | Called on every status read, including the terminal one. Receives (status, snapshot); the snapshot adds next_action, started_at, finished_at, cancelable. |

It resolves for any terminal status, not just completed — a failed run is a result, so read run.status and run.error exactly as a webhook handler would. An API error (401, 404, 5xx) throws SumeRunRequestError carrying status and body.

Prefer a webhook where you can: waitForRun is a poll loop, and run webhooks deliver the identical receipt without one.

One call: create and wait

subscribeFormatRun is createFormatRun* plus waitForRun in one call. It takes either a vanity path or an opaque format_id, forwards Idempotency-Key, and resolves with the terminal receipt.

import { createSumeClient, subscribeFormatRun } from "@sume-com/sdk";

const client = createSumeClient({
  apiKey: process.env.SUME_API_KEY!,
  baseUrl: "https://api.dev.sume.com", // prod default is https://api.sume.com
});

const run = await subscribeFormatRun({
  client,
  path: { handle: "acme", slug: "product-promo" },
  idempotencyKey: `order-${orderId}`,
  body: { input: { product_url: "https://shop.example.com/p/8823" } },
  onStatus: (status, snapshot) => console.log(status, snapshot.next_action),
});

if (run.status === "completed") console.log(run.primary_output_url);
  • Default timeout is 20 minutes, not waitForRun's 10 — video Formats routinely run 10–20.
  • Resolves for any terminal status, exactly like waitForRun. It throws only when the create call itself is refused, since there is no run to wait for.
  • An idempotent replay of an already-finished run returns immediately without polling.
  • There is no event stream to subscribe to. events_url is null on every run today, so onStatus reflects status polling — real, but not a log feed. next_action is the field worth branching on: it separates poll_status from fix_input.

Live Commerce, end to end

The full partner path: upload optional imagery, invoke a team Format, read one durable video URL.

import {
  createSumeClient,
  subscribeFormatRun,
  uploadFile,
  LIVE_COMMERCE_OUTPUT_SCHEMA,
  LIVE_COMMERCE_PRIMARY_OUTPUT_KEY,
  type LiveCommerceFormatInput,
} from "@sume-com/sdk";

const client = createSumeClient({
  apiKey: process.env.SUME_TEAM_API_KEY!, // team key — see below
  baseUrl: "https://api.dev.sume.com",
});

// Optional: your own imagery, uploaded to a durable Sume URL.
const { url } = await uploadFile({
  client,
  file: await fs.openAsBlob("./hero.png"),
  filename: "hero.png",
});

const input: LiveCommerceFormatInput = {
  product_url: "https://shop.example.com/p/8823",
};

const run = await subscribeFormatRun({
  client,
  path: { handle: "mobidoo", slug: "live-commerce" },
  idempotencyKey: `order-${orderId}`,
  body: {
    input: { ...input, hero_image_url: url },
    generation_spend_cap_usd: 3,
    output_schema: LIVE_COMMERCE_OUTPUT_SCHEMA,
    primary_output_key: LIVE_COMMERCE_PRIMARY_OUTPUT_KEY,
    // Or skip the wait entirely and take the webhook:
    // communication: { webhook_url: "https://partner.example/hooks/sume" },
  },
});

if (run.status === "completed") {
  console.log(run.primary_output_url);
  console.log(run.output.live_commerce_video.url); // same URL, named
}

Naming the output, or not

A Format declares no output schema of its own, so this is a choice you make per call:

| | Request | Read the video at | | ----------- | ------------------------------------------- | -------------------------------- | | Default | omit output_schema | output.videos[0].url | | Named | pass output_schema + primary_output_key | output.live_commerce_video.url |

primary_output_url is set either way. Passing primary_output_key: "live_commerce_video" without a schema does nothing: the key has to exist in the projected output to be selected, and the default projection only ever contains text, images, videos, audio, and files.

Prefer the named form. If a run produces no video, a named schema fails loudly with output_schema_unsatisfied, whereas the default projection quietly returns videos: [] and output_error: null.

Team Formats need a team key

A Format owned by a team workspace can only be invoked with an API key issued in that workspace. Being a member of the team is not enough — a personal key is refused:

{
  "error": {
    "code": "workspace_key_required",
    "message": "This Format belongs to a team workspace...",
    "details": { "workspace_id": "org_..." }
  }
}

This is a 403, and it is deliberate. A team Format's runs are billed to the team wallet, counted against the team's quota, and read back through the team's workspace. A personal key would put the spend on your personal wallet while the run belonged to the team — which, before this was enforced, produced runs that generated a real video and then reported output_schema_unsatisfied because the team-scoped harvest could not see media filed under a personal wallet.

So: create the key from the team's dashboard, not your own. A personal key remains correct for your own personal Formats.

A team handle you are not a member of returns 404, indistinguishable from one that does not exist.

Upload a file

uploadFile reserves a presigned URL, PUTs the bytes straight to storage, and completes the asset, resolving with a durable HTTPS URL you can pass as Format input.

const { url, asset_id } = await uploadFile({
  client,
  file: new Blob([bytes], { type: "image/png" }),
  filename: "hero.png",
});
  • contentType is required unless file is a Blob carrying a type.
  • Failures throw SumeUploadError with step set to "create" | "put" | "complete".
  • The bytes never pass through the Sume API, so upload speed is between you and storage.
  • The PUT reuses the fetch you gave createSumeClient, so a proxied client stays proxied.

Verify a webhook

import { verifyWebhook } from "@sume-com/sdk";

export async function POST(request: Request) {
  const body = await request.text(); // raw, before any JSON.parse

  const ok = await verifyWebhook({
    body,
    headers: request.headers,
    secret: process.env.SUME_WEBHOOK_SECRET!,
  });
  if (!ok) return new Response("bad signature", { status: 401 });

  const event = JSON.parse(body);
  await recordTerminalRun(event.request_id, event); // dedupe on request_id
  return new Response(null, { status: 204 }); // fast 2xx, then work
}
  • Async, because it uses WebCrypto rather than node:crypto — that is what keeps the package importable from Workers, Deno, and bundlers that refuse node: specifiers.
  • Pass the raw body. A parsed-and-reserialized object does not verify; key order and whitespace are part of what was signed. In Express, mount express.raw({ type: "application/json" }) on the webhook route only.
  • headers accepts a Headers, a Map, or a plain object (Node's req.headers), and is case-insensitive.
  • Returns false rather than throwing on a malformed delivery — a missing header is a failed verification, which is what you want to branch on.
  • toleranceSeconds defaults to 300. Set 0 to skip the replay-window check.

One verifier covers both surfaces: run webhooks (*.run.terminal) and generation-job webhooks (job.*) share the sume-v1 HMAC-SHA256 scheme over <timestamp>.<raw_body>. The payloads differ; the signature does not. Route on event.

Generate

Regenerate from the committed OpenAPI snapshot:

# Rebase on origin/main first — local checkouts can lag behind the 78-path schema.
pnpm --filter @sume-com/sdk generate

Input is always apps/docs/public/api/openapi.json (relative from this package). There is no live-URL mode in the generator config.

Drift gate

src/generated is committed, so it can go stale when the snapshot moves. CI runs:

pnpm --filter @sume-com/sdk check

This regenerates into a temporary directory and compares — it never touches your working tree — and fails if any file was added, removed, or changed. The fix is always pnpm --filter @sume-com/sdk generate, then commit the result.

Snapshot-sync PRs from sync-docs-openapi-snapshot.yml regenerate the client in the same commit, so they stay green without manual work.

src/create-client.ts, src/wait-for-run.ts, and src/verify-webhook.ts are hand-written and untouched by the generator.

Manual live-drift probe

npx @hey-api/[email protected] -i https://api.sume.com/reference/json -o /tmp/sdk-drift -c @hey-api/client-fetch

Publishing

Workspace consumers resolve exports straight to src/index.ts; publishConfig swaps that for dist at publish time, so npm consumers get compiled JS and .d.ts without the workspace needing a build step.

tsc copies import specifiers into dist verbatim, so every relative import — hand-written and generated — carries an explicit .js extension. Without it node rejects dist/index.js with ERR_UNSUPPORTED_DIR_IMPORT, which is what burned 0.1.1. Two things hold the line: this package typechecks under moduleResolution: nodenext, which makes an extensionless relative import a compile error, and openapi-ts.config.ts sets output.module.extension so the generated tree matches.

.github/workflows/sdk-release.yml runs the drift gate, typecheck, tests, build, and a tarball import smoke test, then publishes:

git tag sdk-v0.2.0 && git push origin sdk-v0.2.0

Bump version in package.json on main before tagging; the workflow refuses a tag that disagrees with the manifest.

workflow_dispatch defaults to dry_run: true — it packs and stops.

Trusted Publishing

Authentication is npm Trusted Publishing over OIDC. There is no NPM_TOKEN, and no long-lived credential to rotate: npm mints a short-lived token for the run after checking the request came from sumelabs/sume-com running sdk-release.yml.

Three constraints follow from that, all encoded in the workflow:

  • Do not rename sdk-release.yml. The trusted publisher is bound to the workflow filename; renaming it breaks publishing until the npm UI is updated to match. Same for moving the publish step into a reusable workflow.
  • The publish job must stay on a GitHub-hosted runner. OIDC is not supported from self-hosted runners, so this workflow pins ubuntu-latest instead of the monorepo's CI_RUNS_ON.
  • pnpm packs, npm publishes. publishConfig field overrides are a pnpm feature that npm does not apply, and pnpm cannot do the OIDC exchange — so pnpm pack builds the tarball and npm publish <tarball> authenticates. npm must be ≥ 11.5.1, newer than what Node 22 bundles.

Provenance attestations are disabled (NPM_CONFIG_PROVENANCE=false): npm cannot attest builds from a private source repo.