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

@apicity/cost

v0.7.3

Published

Pure-table cost & token estimation across @apicity providers — zero network calls, all rates bundled.

Readme

@apicity/cost

Cross-provider cost & token estimation for the apicity monorepo: a USD figure for a planned API call to the billed providers (openai, anthropic, xai, kimicoding, fireworks, alibaba, kie, elevenlabs — see Coverage), computed purely locally from bundled rate tables — no keys, no network.

Zero dependencies, and not a wrapper for any single upstream API — a deliberate cross-provider helper. Provider packages keep their runtime pay-gate verifier bundled locally; this package remains the canonical place to estimate costs, mint OTPs, and document the shared OTP format.

Install

npm install @apicity/cost
# or
pnpm add @apicity/cost

Usage

c.estimate(req) takes the exact JSON body you would POST upstream and lightly parses out the price-affecting fields (model, resolution, duration, message contents, etc.) — the object you build for the real call doubles as the estimate input.

import { createCost } from "@apicity/cost";

const c = createCost(); // no options, no keys — pure local math

// openai chat — same body you'd POST to /v1/chat/completions
const a = c.estimate({
  provider: "openai",
  payload: {
    model: "gpt-5",
    messages: [{ role: "user", content: "Estimate this prompt's cost." }],
    max_tokens: 1000,
  },
});
// → { usd: 0.01..., source: "tokens-heuristic+table", breakdown: { inputTokens: 8, outputTokens: 1000, ... } }

// kie — same body you'd POST to /api/v1/jobs/createTask
const k = c.estimate({
  provider: "kie",
  payload: {
    model: "bytedance/seedance-2",
    input: {
      prompt: "...",
      first_frame_url: "https://...",
      resolution: "720p",
      duration: 8,
      web_search: false,
    },
  },
});
// rate entry reads model + input.resolution + first_frame_url presence (i2v)
// + input.duration → seedance-2-720p-i2v rate × 8 seconds

// kie endpoints whose pricing isn't keyed by payload.model (e.g. Suno) take
// an explicit `endpoint` discriminator that wins the pricing lookup
const s = c.estimate({
  provider: "kie",
  endpoint: "suno/generate",
  payload: { model: "V5_5", prompt: "..." },
});

// elevenlabs TTS — payload is the /v1/text-to-speech body
const e = c.estimate({
  provider: "elevenlabs",
  payload: { model_id: "eleven_flash_v2_5", text: "Hello world" },
});

// free → always $0
const z = c.estimate({ provider: "free-media-upload" });

estimate() is synchronous — nothing to await.

Return shape

interface CostEstimate {
  usd: number;
  currency: "USD";
  source:
    | "tokens-heuristic+table" // openai/anthropic/xai/kimicoding/fireworks/alibaba — chars/4 ≈ tokens × bundled rate
    | "per-unit-table" // elevenlabs/kie — payload-derived units × bundled rate
    | "free";
  breakdown: {
    inputTokens?: number;
    outputTokens?: number;
    units?: number;
    unit?:
      | "tokens"
      | "characters"
      | "seconds"
      | "images"
      | "songs"
      | "generations";
    inputUsdPerMillion?: number;
    outputUsdPerMillion?: number;
    perUnitUsd?: number;
  };
  rateAsOf: string | null; // YYYY-MM-DD — the rate entry's as-of date, falling back to PRICING_AS_OF
  warnings: string[]; // non-empty when fallback fired (unknown model, missing max_tokens, missing duration, etc.)
}

source is the load-bearing field: per-unit-table is exact when the bundled rate is current; tokens-heuristic+table (chars/4 ≈ tokens) is rougher — treat it as ±20%. Nothing calls upstream.

How payloads are parsed

Each provider has a small extractor in src/extract/ that pulls the fields the rate table discriminates on. Unrecognized payloads return usd: 0 plus a warning instead of throwing — a missing input.resolution on a kie seedance payload, or a model not in the bundled table, yields a diagnosable CostEstimate, never an exception.

Text providers (openai / anthropic / xai / kimicoding / fireworks / alibaba): the extractor flattens the chat messages array (or input / prompt / text) into one string for token counting; non-text content parts (images, audio, tool calls) are dropped.

Per-unit providers (kie / elevenlabs): payload-shape knowledge lives in each rate entry's closures in src/pricing/kie.ts / src/pricing/elevenlabs.tsunits(payload) derives the billable quantity (seconds, characters, images) and ordered select pickers resolve the rate variant from fields like input.resolution and input.first_frame_url (i2v vs t2v). Image models price per image; resolution-tiered families require input.resolution; endpoint-keyed pricing (e.g. Suno) uses the EstimateRequest.endpoint discriminator instead of payload.model.

Bundled pricing

Rates are frozen at PRICING_AS_OF (currently 2026-04-30; individual entries may carry their own as-of date) and shipped in src/pricing/ as per-provider modules covering each provider's most common models. Unknown models return usd: 0 plus a warning, never throw.

import { PRICING, PRICING_AS_OF } from "@apicity/cost";

Maintenance is manual: re-fetch the upstream pricing page, edit the provider's module in src/pricing/, bump PRICING_AS_OF.

Coverage

| Provider | source | Notes | | ------------------------------------------------------------------ | ------------------------ | ----------------------------------------------------------------------------------- | | openai, anthropic, xai, kimicoding, fireworks, alibaba | tokens-heuristic+table | chars/4 ≈ tokens — no upstream call | | elevenlabs | per-unit-table | priced per character | | kie | per-unit-table | per second of video / per image / per generation; endpoint discriminator for Suno | | free | free | always $0 |

Paid endpoint guard (OTP pay gate)

The cost package keeps a small, explicit paid-endpoint registry used for OTP minting and documentation. Provider packages bundle the matching runtime registry for their own paid endpoints. Endpoints not in the registry are assumed free and need no caller changes.

Paid endpoints require a single-use OTP (one-time password) minted from a shared HMAC secret. The gate is fail-closed and does no cost estimation — pure authorization: a paid call cannot fire unless the provider was constructed with the secret and the caller presents a valid, request-bound OTP. The autonomous caller never holds the secret, so it cannot self-approve; only the human or code client that holds the secret can mint. There are no environment variables and no key files — the secret is passed in via factory options (or the MCP server's --paygate-secret-file).

Registry model

  • PAID_ENDPOINTS is the canonical list: exact (provider, method, dotPath) triples — no regex, prefix, wildcard, or inferred matching.
  • Unlisted endpoints pass through free, with no OTP or configuration.
  • Listed endpoints block unless a valid OTP is supplied.

Token format

OTP tokens are a dependency-free compact envelope:

<base64url(payloadJson)>.<base64url(HMAC-SHA256(payloadSegment, secret))>

The signature is HMAC-SHA256 over the exact base64url payload segment bytes, verified in constant time. Payload schema:

interface PayGateOtpPayload {
  v: 1; // version
  jti: string; // random 128-bit hex (unique token id)
  provider: string; // e.g. "kie"
  method: string; // e.g. "POST"
  dotPath: string; // e.g. "api.v1.jobs.createTask"
  requestHash: `sha256:${string}`; // sha256 of canonical request JSON
  iat: number; // issued-at unix seconds
  exp: number; // expiration unix seconds
}

Configuration

The code client supplies a PayGateConfig via factory options:

interface PayGateConfig {
  secret: string; // shared HMAC secret (the code client holds it)
  replayStore?: ReplayStore; // defaults to an in-process Set, per provider instance
  now?: () => number; // clock injection for tests; defaults to Date.now
}

interface ReplayStore {
  has(jti: string): boolean;
  add(jti: string): void;
}
import { createKie } from "@apicity/kie";

const provider = createKie({
  apiKey: process.env.KIE_API_KEY!,
  paygate: { secret: loadSecret() }, // from your secret manager / config
});

Minting OTPs

mintOtp is pure and env-free — the secret is passed explicitly and the OTP binds to the exact request via its canonical hash:

import { mintOtp } from "@apicity/cost";

const otp = mintOtp(secret, {
  dotPath: "api.v1.jobs.createTask", // provider/method resolved from the registry
  request: payload, // bound by canonical hash
  ttl: "10m", // seconds or "10m" / "1h" / "1d"; defaults to 10m
});

Request canonicalization

Before hashing, the payload is canonicalized — serialized to JSON with recursively sorted object keys, array order preserved, non-JSON values (functions, undefined, circular references) rejected — then SHA-256 hashed and prefixed sha256:. Change any byte of the request and verification fails.

Replay protection

Each OTP jti is single-use. The default ReplayStore is an in-process Set scoped to one provider instance (no files, no XDG_STATE_HOME); pass a custom replayStore for cross-process or persistent protection. The jti is consumed before dispatch — see Retry semantics.

Public interface

interface PayGateApproval {
  otp: string;
}

async function dispatchWithPaidGate<T>(
  provider: string,
  method: string,
  dotPath: string,
  payload: Record<string, unknown>,
  approval: PayGateApproval | undefined,
  dispatch: () => Promise<T>,
  config?: PayGateConfig
): Promise<T>;

Paid endpoint APIs accept the approval as a second options object:

const task = await provider.post.api.v1.jobs.createTask(
  { model: "kling-3.0/video", input: { prompt: "...", duration: "5" } },
  { otp }
);

Guard behavior

Each failed check throws PayGateError with the code shown:

  1. Preflight — endpoints not in PAID_ENDPOINTS dispatch immediately.
  2. Configuration — paid endpoint with no paygate.secretpaygate-not-configured.
  3. OTP presence — missing approval.otpotp-missing.
  4. Signature — constant-time HMAC check of the payload segment → otp-invalid-signature on mismatch.
  5. Expirationexp in the past → otp-expired.
  6. Request bindingprovider, method, dotPath, and requestHash must match the actual call → otp-mismatched-request.
  7. Replay checkjti already in the store → otp-replayed.
  8. Consume + dispatch — the jti is recorded, then the HTTP request fires.
import { PayGateError } from "@apicity/cost";

try {
  await provider.post.api.v1.jobs.createTask({ ... }, { otp });
} catch (e) {
  if (e instanceof PayGateError) {
    // e.code: paygate-not-configured | otp-missing | otp-malformed
    //         | otp-invalid-signature | otp-expired
    //         | otp-mismatched-request | otp-replayed
  } else throw e;
}

Failure modes

| Condition | PayGateError.code | | ---------------------------------- | ------------------------ | | Provider built without a secret | paygate-not-configured | | Paid endpoint without OTP | otp-missing | | Malformed envelope | otp-malformed | | Invalid HMAC signature | otp-invalid-signature | | Expired OTP (exp < now) | otp-expired | | Mismatched provider/method/dotPath | otp-mismatched-request | | Mismatched request hash | otp-mismatched-request | | Replayed OTP (jti seen) | otp-replayed |

CLI: minting OTPs

The apicity-paygate binary mints OTPs. The secret is read from a file (never an env var); only the OTP is printed to stdout:

apicity-paygate otp mint \
  --secret-file ./paygate.secret \
  --dot-path api.v1.jobs.createTask \
  --payload-file request.json \
  --ttl 10m

Wiring the gate into a custom provider

@apicity/kie and @apicity/xai bundle their runtime gate locally so the provider packages stay standalone. This package still exports withPaidGate and dispatchWithPaidGate for custom provider trees that want the same behavior. The walker descends the HTTP-method roots (post, get, delete, patch, put) and routes every leaf whose (provider, method, dotPath) is in PAID_ENDPOINTS through dispatchWithPaidGate; free leaves pass through unchanged, and schema records and other non-route properties are returned by reference.

import { createReplayStore, withPaidGate } from "@apicity/cost";

export function createKie(opts: KieOptions): KieProvider {
  const paygate = opts.paygate
    ? {
        ...opts.paygate,
        replayStore: opts.paygate.replayStore ?? createReplayStore(),
      }
    : undefined;

  // ...build endpoint functions...
  return withPaidGate(
    "kie",
    {
      veo: withPaidGate("kie", createVeoProvider(...), { config: paygate }),
      modelInputSchemas,               // data, untouched
      post: { api: { v1: { jobs: { createTask: Object.assign(createTask, { schema }) } } } },
      get:  { api: { v1: { jobs: { recordInfo } } } },
    },
    { config: paygate }
  );
}

The gate is generic for custom trees, but published provider packages should not depend on @apicity/cost for runtime gating.

Retry semantics

The OTP jti is consumed before dispatch() runs: if dispatch later fails (network error, upstream 5xx, abort), it stays consumed and the caller must mint a fresh OTP to retry. This is intentional — otherwise a hostile caller could replay a single OTP on every transient failure. Each OTP is single-use authority for one network attempt.

MCP server

@apicity/mcp-server is the code client: started with --paygate-secret-file <path>, it holds the secret to verify OTPs (it never mints). A human mints an OTP out-of-band with the same secret and the caller passes it as the paid tool's otp argument — an AI driving the tool cannot self-approve.

Minimal operator workflow

  1. Generate a secret (one-time) and store it (secret manager / file).
  2. Prepare a request JSON file.
  3. Mint an OTP with the CLI above.
  4. Pass the OTP to the caller (copy-paste, secrets manager, etc.).
  5. Caller uses it:
    await provider.post.api.v1.jobs.createTask({ ... }, { otp: "<paste>" });

Out of scope

  • Anthropic prompt-cache pricing (rates are in the table but estimate() ignores them — assumes no caching)
  • Batch API discount (50% off across providers)
  • Tier-based fallback for fireworks (parameter-count brackets)
  • Suno per-song pricing on kie (no stable published rate)
  • Caller-side pricingOverrides

Part of the apicity monorepo.

License

MIT — see LICENSE.