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

@voxly-vision/sdk

v0.3.3

Published

Marketing compliance and advertising compliance SDK for Node.js and TypeScript. Review image and video ads against regulatory codes and brand guidelines with Voxly Vision.

Readme

Voxly Vision — Marketing Compliance SDK for Node.js and TypeScript

npm

The official Voxly Vision SDK for marketing compliance, advertising compliance and brand compliance. Add automated image and video ad review to your marketing workflows: evaluate creative assets against regulatory codes and brand guidelines, then retrieve framework verdicts, section-level reasoning and recommendations.

Built for developers supporting marketing teams, compliance reviewers, brands and agencies. Use it to check campaign creative before publication, surface findings in approval tools, or connect compliance review to your digital asset management (DAM) and content management (CMS) workflows. Your reviewers retain final sign-off.

Product website · Create an account · Open your workspace · API documentation · Contact the team

Marketing compliance use cases

  • Advertising review: check image and video ads against the live regulatory frameworks available in your workspace.
  • Brand compliance: evaluate creative against custom brand standards authored and approved in the Voxly Vision portal.
  • Alcohol marketing compliance: build review workflows around relevant codes, such as ASA CAP, Portman Group, DISCUS and TTB, where those frameworks are available in your workspace.
  • Campaign approval workflows: collect classifications, justifications and section recommendations for human review, with webhooks for asynchronous results.

Use voxly.frameworks.list({ status: "live" }) to discover the frameworks your workspace can evaluate against. The SDK handles uploads, polling, retries and webhook verification.

Works with Node.js 20+, in TypeScript or JavaScript, ESM or CommonJS. No runtime dependencies.

Install

npm install @voxly-vision/sdk

You can also use pnpm add @voxly-vision/sdk or yarn add @voxly-vision/sdk.

Get an API key

  1. Create a Voxly Vision account, or sign in if you already have one.
  2. Open your workspace and select the workspace you want to evaluate assets in.
  3. Open Settings → API keys → Create API key. Give the key a name and choose its expiry.
  4. Copy the key when it is shown; it will not be displayed again. Store it in your server's environment or secret manager as VOXLY_API_KEY.
export VOXLY_API_KEY="your-workspace-api-key"

Keys are workspace-scoped. If you cannot create one, contact your workspace administrator or the Voxly Vision team. Evaluations use your workspace's Lumen balance; check usage and billing in the app before running a large batch.

Quick start

import { VoxlyVision, formatEvaluation } from "@voxly-vision/sdk"

const voxly = new VoxlyVision({ apiKey: process.env.VOXLY_API_KEY })

const framework = await voxly.frameworks.find("ASA CAP Code")

const result = await voxly.evaluateAndWait({
	name: "Q3 hero video",
	files: ["./hero.mp4", "./thumbnail.png"],
	frameworks: [framework],
	wait: { onProgress: e => console.log(e.status) },
})

console.log(formatEvaluation(result, { sections: true }))

That one call uploads the files, submits the evaluation, polls until every asset is finished, and fetches the section-level detail for each.

Choose a framework available in your workspace; the example uses ASA CAP Code. Results classify content as compliant, non_compliant or non_applicable. A job's completed status means processing finished, not that the creative passed every rule.

See the public API documentation. The complete SDK method, argument and error reference is included as API.md in the installed npm package.

Authentication

After creating a workspace API key, the client reads it from VOXLY_API_KEY if you do not pass it:

const voxly = new VoxlyVision() // uses process.env.VOXLY_API_KEY

An API key is a workspace secret. Keep it on your server. The client refuses to run in a browser unless you pass dangerouslyAllowBrowser: true.

Point at another environment with baseUrl or VOXLY_BASE_URL. The default is https://api.voxlyvision.com/api.

Evaluating assets

evaluate() returns as soon as the API accepts the work. evaluateAndWait() also waits for the results. Both take the same input.

const accepted = await voxly.evaluate({
	name: "Spring campaign",
	frameworks: ["<framework id>", "<framework id>"], // up to 3
	files: ["./a.png", "./b.mp4"], // up to 5
})
accepted.evaluationId // poll this, or wait on it

Supply the assets exactly one way per call:

| Field | Use when | | ---------- | ----------------------------------------------------------------------------------------- | | files | You have the bytes: a path, a Buffer, a Blob/File, or { data, name, contentType } | | fileUrls | The assets are at public HTTPS URLs; Voxly Vision fetches them | | assetIds | Re-run assets already in the workspace |

Accepted media: image/jpeg, image/png, video/mp4, up to 500 MB each. Content type is inferred from the file extension or the Blob type; pass { data, name, contentType } to set it explicitly.

Small requests are posted inline. Larger ones go through presigned uploads: the SDK asks the API for an upload URL per file, PUTs the bytes straight to storage, then submits with the upload ids. This happens automatically; there is no separate upload step, and an asset only ever enters the workspace as part of an evaluation.

Idempotency

Every submission carries an idempotency key, and automatic HTTP retries reuse the exact submission. Inline file keys include a SHA-256 digest of the bytes; URL and asset submissions use their references (the SDK does not download URLs to hash their contents). Identical submissions reuse the original evaluation while the server retains its idempotency record. To deliberately run a new evaluation, pass a fresh UUID from crypto.randomUUID().

Large files receive new upload IDs each time you call evaluate(). For manual retries after an uncertain submission outcome, prepare once and retain the returned object:

const prepared = await voxly.evaluations.prepare({
	name: "Campaign review",
	frameworks: [frameworkId],
	files: ["./hero.mp4"],
})
const accepted = await prepared.submit({ signal: controller.signal })
// If submission fails transiently, retry prepared.submit() on this same object.
// It keeps the same upload IDs, body and idempotency key without uploading again.

Prepared submissions live in memory and are not serializable; keep referenced local files unchanged until submission finishes. Preparation may upload files, but does not start an evaluation. The preparation signal applies only to preparation; pass a signal separately to each submit() call. After acceptance, save the evaluation ID and use waitForEvaluation() to resume waiting, including after a process restart.

A caller-supplied idempotencyKey must be a UUID and must represent the same request. Re-uploading large files with an old key produces a conflict because upload IDs changed. The server compares inline file metadata rather than bytes for explicit keys, so never reuse an explicit key for changed content. Default inline keys changed in 0.3.0 to include bytes; they do not replay evaluations submitted by older SDK versions.

Formatting results

import { formatEvaluation } from "@voxly-vision/sdk"

const outcome = await voxly.waitForEvaluation(accepted.evaluationId)
console.log(formatEvaluation(outcome))
console.log(formatEvaluation(outcome, { sections: true }))

formatEvaluation() returns plain text without logging or modifying the result. It accepts both evaluation snapshots and hydrated outcomes, includes failures, and optionally shows section verdicts and recommendations when detail is available. A completed job is not necessarily compliant; the formatter displays the actual classification.

Waiting for results

const outcome = await voxly.waitForEvaluation(accepted.evaluationId, {
	pollIntervalMs: 2_000, // grows to maxPollIntervalMs (10 s)
	timeoutMs: 15 * 60 * 1000,
	signal: controller.signal,
	onProgress: e =>
		console.log(
			e.status,
			e.jobs.map(j => j.status),
		),
})

outcome.status // "completed", or "failed" if any job failed
outcome.jobs // each with .detail: the full job, sections included
outcome.succeeded // jobs that completed
outcome.failed // jobs that failed, with .results[].error

If the deadline passes first, EvaluationTimeoutError carries lastSeen (the last snapshot; its finished jobs are complete and usable) and pendingJobIds, so you can wait again later.

Webhooks

For a deployed integration, register an endpoint and let Voxly Vision call you instead of polling. Events: job.completed, job.failed, evaluation.completed.

const { endpoint, secret } = await voxly.webhooks.create({
	url: "https://example.com/voxly/webhook",
	events: ["evaluation.completed"],
})
// Store `secret`. It is shown only here and on rotation.

await voxly.webhooks.ping(endpoint.id) // sends a test event and returns what your endpoint answered

Verify incoming requests with the raw body and the Voxly-Signature header:

// Express
app.post("/voxly/webhook", express.raw({ type: "*/*" }), (req, res) => {
	let event
	try {
		event = voxly.webhooks.constructEvent(
			req.body,
			req.headers["voxly-signature"],
			process.env.VOXLY_WEBHOOK_SECRET,
		)
	} catch {
		return res.status(400).send("bad signature")
	}
	res.sendStatus(200) // acknowledge first, then do the work
	if (event.type === "evaluation.completed") {
		console.log(event.data.evaluationId, event.data.status)
	}
})

// Next.js route handler
export async function POST(req: Request) {
	const event = constructWebhookEvent(
		await req.text(),
		req.headers.get("voxly-signature"),
		process.env.VOXLY_WEBHOOK_SECRET!,
	)
	// …
	return new Response(null, { status: 200 })
}

Deliveries are retried for about 36 hours on anything other than a 2xx, and re-sent with the same event id, so deduplicate on it. voxly.webhooks.listDeliveries(id) shows every attempt.

Other resources

await voxly.frameworks.list({ status: "live" })
await voxly.frameworks.retrieve(id) // with sections and rules
await voxly.frameworks.find("Diageo") // by name, live only

await voxly.jobs.retrieve(jobId)
await voxly.jobs.delete(jobId)

await voxly.assets.list({ q: "hero", order: "desc", page: 1, limit: 50 })
for await (const asset of voxly.assets.iterate({ labelIds: [labelId] })) {
	/* … */
}
await voxly.assets.delete(assetId)

Errors

Everything the SDK throws extends VoxlyVisionError.

| Error | When | | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------- | | ValidationError | A local check failed; no request was made | | APIError | Non-2xx response. .status, .code, .body, .requestId | | BadRequestError (400), AuthenticationError (401), PermissionDeniedError (403), NotFoundError (404), ConflictError (409), RateLimitError (429), InternalServerError (5xx) | Subclasses of APIError | | APIConnectionError | No response at all | | APITimeoutError | Exceeded timeoutMs | | EvaluationTimeoutError | Polling gave up; carries what finished | | WebhookSignatureError | constructEvent refused the request |

ConflictError means an idempotency key was reused for different content.

Retries and timeouts

Safe requests (reads, deletes, and submissions carrying an idempotency key) are retried on network errors, 429 and 5xx with exponential backoff, honouring Retry-After. Configure with maxRetries (default 2) and timeoutMs (default 60 s; inline media submissions get 5 minutes). Every method accepts an AbortSignal.

Client options

new VoxlyVision({
	apiKey, // or VOXLY_API_KEY
	baseUrl, // or VOXLY_BASE_URL
	timeoutMs: 60_000,
	maxRetries: 2,
	fetch, // custom fetch, e.g. behind a proxy
	userAgentSuffix: "my-app/1.2",
})

Support

For API access, integration help, or SDK issues, contact [email protected]. Include your SDK version and the request ID from an error where available; do not include API keys or confidential creative assets.

Explore Voxly Vision for marketing compliance workflows, or read the API documentation for authentication and endpoint details.

License and API access

This SDK's code and included documentation are licensed under the MIT License. You may use, modify and redistribute the SDK, including in commercial applications, provided you retain the copyright and license notices. The full license is included as LICENSE in this package.

The MIT license covers this SDK package only. It does not license the Voxly Vision backend, hosted application, proprietary compliance frameworks or service data. It does not grant API access, free evaluations or permission to bypass authentication, usage limits or billing. Use of the hosted service requires an authorized account and API key and remains subject to the applicable service agreement and subscription or usage charges. No rights to Voxly Vision or Voxly Digital trademarks are granted by this SDK license.

Development

pnpm install
pnpm test        # unit tests against an in-process fake API
pnpm build       # dist/ (ESM + CJS + types)
pnpm lint
VOXLY_CONTRACT=1 VOXLY_API_KEY=… VOXLY_BASE_URL=… pnpm test:contract  # against a live environment

Releases use changesets and publish from GitHub Actions through npm trusted publishing.