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

@wahlu/api-client

v0.7.3

Published

Typed, runtime-independent client for the Wahlu public API

Readme

@wahlu/api-client

Typed, standards-based client for the Wahlu public API. It is the shared transport boundary for the Wahlu CLI and MCP server; it contains no database, provider, media-processing, or scheduling logic.

Release status: the canonical 22-operation client is ESM-only and requires Node.js 18+ or an equivalent modern runtime with standards-based fetch, Web Crypto, and AbortController.

npm install @wahlu/api-client

Pass the API key at runtime through createWahluClient({ apiKey }). Never embed a Wahlu API key in a browser bundle, committed source, URL, command argument, or log. Server and local-agent processes should load it from their secret store or environment and grant only the scopes and brands the workflow needs.

The canonical surface contains exactly twenty-two operations:

  • Discovery: getContext(), listTargets(), getTargetDynamicOptions(), refreshTargetDynamicOptions(), getPlatformCapabilities()
  • Media: importMediaFromUrl(), createMediaUploadSession(), listMedia(), getMedia(), createMediaRepairDerivative()
  • Content: listContentItems(), getContentItem(), createContentDraft(), updateContentDraftTikTokPrivacy(), preflightContentSchedule()
  • Schedules: listSchedules(), createSchedule(), getSchedule(), getScheduleReceipt(), cleanupProviderPublications(), rescheduleSchedule(), cancelSchedule()

There is no hidden polling, execution, retry, or generic transport method in this surface. Schedule listing requires an explicit UTC date range of at most 93 days. Rescheduling and cancellation are explicit, idempotent commands with exact Schedule confirmation; cancellation is limited to unsent Schedules with no execution history and retains the audit record.

For TikTok, grant integrations:read and call getTargetDynamicOptions(brandId, integrationId) for the exact selected target immediately before choosing privacy. This compatible GET is strictly read-only: it never refreshes credentials, acquires a provider-effect lease, or changes integration status. If the current credential cannot safely support the read, it fails explicitly. A caller with integrations:write can then make the separate medium-risk refreshTargetDynamicOptions(brandId, integrationId) POST; that action may rotate stored credentials or update integration reauthorisation state, but changes no content, creates no Schedule, and submits no provider post. It accepts a strict empty body and no idempotency key. Pass one returned value to updateContentDraftTikTokPrivacy(brandId, contentItemId, { integration_id, privacy_level }) to update that same draft. Never guess PUBLIC_TO_EVERYONE: the API re-queries TikTok and rejects a stale or unsupported value before writing. The update creates no replacement draft or Schedule and sends no provider post.

import { createWahluClient } from "@wahlu/api-client";

const wahlu = createWahluClient({ apiKey: process.env.WAHLU_API_KEY! });
const { data: context } = await wahlu.getContext();
const brand = context.brands[0];
if (!brand) throw new Error("This API key has no accessible brands.");

const { data: targetDiscovery } = await wahlu.listTargets(brand.id);
const target = targetDiscovery.targets.find(
  (candidate) =>
    candidate.platform === "instagram" &&
    candidate.schedulable &&
    candidate.integration_id &&
    candidate.capabilities.scheduling &&
    candidate.capabilities.media_upload,
);
if (!target?.integration_id) throw new Error("No ready Instagram target is available.");
// The target already embeds its effective capabilities. Call
// getPlatformCapabilities() only when you need the full platform configuration registry.

const { data: imported } = await wahlu.importMediaFromUrl(
  brand.id,
  { url: "https://assets.example.com/campaign/hero.jpg" },
  { idempotencyKey: "media-launch-hero-v1" },
);
const { data: media } = await wahlu.getMedia(brand.id, imported.id);
if (media.links.self.href !== imported.links.self.href) {
  throw new Error("The media hand-off changed identity.");
}
if (media.status !== "completed" || !media.readiness.ready_for_content) {
  const nextAction = media.readiness.next_actions[0];
  throw new Error(nextAction?.guidance ?? `Media is not ready: ${media.status}`);
}

const { data: draft } = await wahlu.createContentDraft(
  brand.id,
  {
    name: "Launch announcement",
    copy_mode: "single",
    single_copy: { caption: "We are live", hashtags: ["launch"] },
    instagram_settings: { media_ids: [media.id], post_type: "GRID_POST" },
    intended_integration_ids: [target.integration_id],
  },
  { idempotencyKey: "draft-launch-announcement-v1" },
);

const scheduledAt = "2026-08-01T10:00:00+10:00";
const { data: preflight } = await wahlu.preflightContentSchedule(
  brand.id,
  draft.content_item.id,
  {
    integration_ids: [target.integration_id],
    scheduled_at: scheduledAt,
    approval_status: "pending_review",
  },
);
if (!preflight.can_schedule || !preflight.links.create_schedule) {
  throw new Error(preflight.blockers[0]?.message ?? "Not schedulable");
}
const preflightSchedule = preflight.request;
if (!preflightSchedule.scheduled_at || preflightSchedule.approval_status !== "pending_review") {
  throw new Error("Preflight did not preserve the held Schedule decision.");
}

const { data: created } = await wahlu.createSchedule(
  preflight.content_item.brand_id,
  {
    content_item_id: preflight.content_item.id,
    integration_ids: preflightSchedule.integration_ids,
    scheduled_at: preflightSchedule.scheduled_at,
    approval_status: preflightSchedule.approval_status,
  },
  { idempotencyKey: "schedule-launch-announcement-v1" },
);
const { data: schedule } = await wahlu.getSchedule(
  created.schedule.brand_id,
  created.schedule.id,
);
console.log(schedule.status, schedule.blocking_reason?.code);

pending_review is an explicit safety boundary. It creates a held Schedule with APPROVAL_PENDING, no execution or job, and no publishing-provider effect. An explicit approved Schedule additionally requires publish:execute permission and may later publish externally.

Resource-creating mutations require a stable caller-owned idempotency key. Persist and reuse the same key when retrying the same logical operation; do not generate a new key for each transport attempt. The precise TikTok privacy PUT is naturally idempotent and does not accept an Idempotency-Key header. The bounded target dynamic-options refresh is an explicit medium-risk POST action with a strict empty body; it likewise accepts no idempotency key.

Safety behaviour

  • API keys are sent only as bearer tokens and are redacted if a server error echoes them.
  • Requests have finite timeouts, support cancellation, and bound response bytes before JSON parsing.
  • Redirects stay on the same origin and cannot silently rewrite mutation methods.
  • Safe reads retry bounded transport and retryable HTTP failures. Mutations retry only with a caller-supplied idempotency key and a descriptor that supports idempotency.
  • Every logical request keeps one request ID across attempts.
  • Requests and responses are validated against the canonical operation descriptors, including strict status, envelope, request identity, and replay agreement.
  • Response identity and pagination are available only through result.meta.request_id and result.meta.pagination; the result object has no duplicate legacy aliases.

Preflight is write-free: it reports readiness and repair actions but creates no Schedule, execution, job, queue entry, or provider effect. getMedia() and getSchedule() each perform one bounded read; the SDK does not invent polling behavior.

Production defaults to https://api.wahlu.com and https://media.wahlu.com. Staging or local environments can set baseUrl and mediaBaseUrl explicitly; plaintext HTTP is accepted only for loopback development origins.