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

@authoritas-ace/ace-sdk

v2.0.0

Published

TypeScript client for the ACE (Agentic Commerce Engine) v1 API: contextual enrichment, durable jobs, projects, experiments, webhooks

Readme

ACE SDK

The official JavaScript and TypeScript client for ACE (Agentic Commerce Engine). Use it in Node.js apps, scripts, or serverless functions to run the Contextual Enrichment Engine, track durable jobs, and manage your projects, experiments and webhooks.

New to ACE? Start at ace.authoritas.com, or read the SDK guide for the long-form version of this page.

Install

npm install @authoritas-ace/ace-sdk

Node 18 or newer. ESM only.

Create a client

import { createClient } from "@authoritas-ace/ace-sdk";

const ace = createClient({
  baseUrl: "https://ace.authoritas.com",
  apiKey: process.env.ACE_API_KEY, // ace_live_... or ace_test_...
});

Create a key under Settings, Developer console, Keys. health() is the only method that works without one.

How results are shaped

Every method resolves to an ApiResult<T>. Nothing throws on an API error, so you get error instead of data, plus the HTTP status:

interface ApiResult<T> {
  data?: T;
  error?: { code: string; message: string; details?: unknown };
  status: number;
  meta?: { pagination?: { page: number; pageSize: number; total: number; totalPages: number } };
}

Check error yourself, or use assertOk to turn a failure into a thrown AceApiError:

import { assertOk, AceApiError } from "@authoritas-ace/ace-sdk";

try {
  const res = await ace.usage();
  assertOk(res);
  console.log(res.data.credits.balance); // res.data is defined past this point
} catch (err) {
  if (err instanceof AceApiError) console.error(err.code, err.status, err.message);
}

Contextual Enrichment Engine

Three methods, matching the three engine endpoints. Each takes a source, which is either inline products or a connected store.

// 1. Generate the contextual rules for a product set.
const rules = await ace.enrichment.rules({
  source: {
    type: "inline",
    products: [{ id: "sku-1", title: "Merino base layer", category: "Outdoor" }],
  },
  creativityLevel: 3,
});

// 2. Generate content grounded in those rules.
const content = await ace.enrichment.content({
  source: { type: "inline", products: [{ id: "sku-1", title: "Merino base layer" }] },
  contentTypes: ["product-description", "meta-tags", "jsonld-schema"],
  rules: rules.data,
});

// 3. Or run both steps in one call.
const pipeline = await ace.enrichment.pipeline({
  source: { type: "inline", products: [{ id: "sku-1", title: "Merino base layer" }] },
  contentTypes: ["product-description", "meta-tags"],
});

A product needs only id and title. Extra keys you pass through (brand, price, tags, attributes) become grounding signal.

Write methods take an idempotency key, so a retried request reuses the original job instead of starting a second one:

await ace.enrichment.pipeline(body, { idempotencyKey: "nightly-2026-07-29" });

Jobs

A large content or pipeline request runs asynchronously and returns a job envelope. Poll it, or let the SDK poll for you.

const started = await ace.enrichment.pipeline({ source, contentTypes: ["product-description"] });
assertOk(started);

const finished = await ace.jobs.wait(started.data.id, { pollMs: 2000, timeoutMs: 300_000 });
const results = await ace.jobs.results(started.data.id, { page: 1, pageSize: 50 });

| Method | What it does | |--------|--------------| | ace.jobs.list({ kind, status, storeId, page, pageSize }) | List jobs, newest first | | ace.jobs.get(id) | One job with its status and result | | ace.jobs.results(id, { page, pageSize }) | Paginated per-product results | | ace.jobs.cancel(id) | Cancel a queued or running job | | ace.jobs.wait(id, { pollMs, timeoutMs }) | Poll until terminal. Returns a TIMEOUT error if the cap elapses |

Projects

await ace.projects.list();                                 // scoped keys see only their project
await ace.projects.get(id);
await ace.projects.create({ name: "Autumn catalogue" });    // integration_type defaults to "feeds"
await ace.projects.update(id, { description: "Q4 push" });
await ace.projects.remove(id);

Experiments

Every experiments call is project-scoped, so projectId is required throughout. On create it travels in the body; everywhere else it is a scope argument.

await ace.experiments.list({ projectId });
await ace.experiments.get(id, { projectId });
await ace.experiments.create({ projectId, name: "PDP copy A/B" });
await ace.experiments.update(id, { status: "running" }, { projectId });
await ace.experiments.remove(id, { projectId });

Webhooks

const hook = await ace.webhooks.create({
  url: "https://example.com/hooks/ace",  // https is required
  events: ["enrichment.job.succeeded", "enrichment.job.failed"],
});

await ace.webhooks.list();
await ace.webhooks.update(id, { is_active: false });
await ace.webhooks.remove(id);

// Delivery log. Page counts arrive in result.meta.pagination.
const log = await ace.webhooks.deliveries(id, { status: "failed", pageSize: 50 });

Each subscription carries a secret. Verify the signature header on your endpoint against it before trusting a payload.

Utilities, usage and feeds

Language detection is unbilled and the scorers are pure functions, so none of these consume credits.

await ace.utils.language({ products });          // dominant locale + confidence
await ace.utils.agenticReadiness({ products });  // per-product readiness + summary
await ace.utils.reviewQuality({ items });        // weighted overall score per item

await ace.usage();                               // credits, rate limit, job counts, test quota
await ace.feeds.list({ projectId });
await ace.feeds.get(feedId);

testQuota is present only for ace_test_ keys.

Escape hatch

Any endpoint without a typed wrapper is still reachable, with auth and error handling applied:

await ace.get("/api/v1/openapi");
await ace.post("/api/v1/some/endpoint", body);
await ace.put("/api/v1/some/endpoint/id", body);
await ace.del("/api/v1/some/endpoint/id");

Also available

  • ACE CLI drives the same API from your terminal.
  • The MCP server lets an AI assistant call ACE for you.

License

MIT