Loading docs...
ZeroClick raises $55M
Loading docs...
ZeroClick lets agents pay for your API without you building agent billing yourself. You list what you sell, route agent traffic through your ZeroClick pay URL, and add a small guard to your API so only ZeroClick-signed requests can reach billable endpoints.
Money goes from the buying agent to your connected Stripe account. ZeroClick handles agent identity, payment challenges, request IDs, and usage records.
Use of ZeroClick is governed by the ZeroClick Seller Terms of Service.
ZeroClick is the payment and access layer in front of your existing API. We manage the MPP and x402 endpoints that AI agents call, pay through, and use to reach your services. You keep your product, your upstream API, your pricing, and your customer experience; ZeroClick turns that into an agent-ready paid endpoint.
Sellers get a hosted pay URL, agent wallet authentication, payment challenges, payment verification, signed proxy requests, request IDs, usage checks, and usage records. You do not need to build a checkout flow for agents, run protocol-specific payment infrastructure, or issue a different API key to every buying agent.
To integrate, you provide the API you already operate, configure services, meters, and plans in the dashboard, connect Stripe, and add a small guard to your billed endpoints. Your server verifies that requests came from ZeroClick, asks ZeroClick whether the requested usage is allowed, returns a standard payment-required response when it is not, and reports usage when work is completed.
Seller is your company or product in ZeroClick. Your seller gets a ZeroClick pay URL like https://acme.pay.zeroclick.io. This is the URL agents call and pay through.
Service is one thing agents buy or use from you. It can be a specific LLM model, a data API, a workflow, or a platform feature, such as gpt-oss-120b, image-search, or product-watch. Usage is reported against a service.
Meter is a billable measurement inside a service, such as requests, output_tokens, product_watch_hours, or search_results.
Plan is how agents pay. A plan can be pay as you go, credit-based, subscription, or subscription with usage. Plans belong to the seller; services and meters describe what was used.
In the dashboard:
usage:read and usage:write scopes.API keys carry one or more scopes:
| Scope | Grants |
|---|---|
admin:read | Read your sellers, services, meters, plans, prices, custom domains, signing-secret metadata, and analytics. |
admin:write | Create, update, and delete that configuration, including creating and revoking signing secrets. It does not include admin:read; grant both for a key that manages configuration. |
usage:read | Check whether requested usage is allowed. |
usage:write | Report billable usage. |
Use admin:* keys for tooling and automation that manages your ZeroClick configuration, and separate usage:* keys for the server integration that checks and reports usage. Keys created before the admin:* scopes existed carried billing:* and secrets:* scopes; they were migrated to the equivalent admin:* scopes with the same access.
After that, agents call your ZeroClick pay host instead of your upstream API:
Agent calls: https://acme.pay.zeroclick.io/v1/say-hello
ZeroClick calls: https://api.acme.com/v1/say-helloYour upstream API should treat every billable endpoint as private to ZeroClick. If a request is missing ZeroClick headers or has an invalid signature, reject it.
Install the web-native seller SDK in your server application:
npm install @zeroclickai/sellersCreate one client for your seller. The keys in signingSecrets are the zcsec_... IDs shown with your signing secrets in the dashboard. Keep the previous secret available while rotating so in-flight requests signed with either kid continue to verify.
import { createSeller } from "@zeroclickai/sellers";
const previousSigningSecret =
process.env.ZEROCLICK_SIGNING_SECRET_PREVIOUS;
export const zeroClick = createSeller({
signingSecrets: {
zcsec_current: process.env.ZEROCLICK_SIGNING_SECRET_CURRENT!,
...(previousSigningSecret
? { zcsec_previous: previousSigningSecret }
: {}),
},
// This key needs both usage:read and usage:write scopes.
apiKey: process.env.ZEROCLICK_API_KEY!,
allowanceUnavailable: "allow",
checkTimeoutMs: 1500,
onAllowanceUnavailable(error) {
console.warn("ZeroClick allowance check unavailable", {
code: error.code,
operation: error.context.operation,
});
},
});The defaults are a five-minute signature tolerance, a 1.5-second allowance timeout, and allowanceUnavailable: "allow". You can omit the explicit values above when those defaults fit your application.
ZeroClick forwards requests to your upstream with these headers:
zc-request-id: zcreq_...
zc-agent-id: agt_... # omitted on an anonymous probe
zc-signature: t=1760000000,kid=zcsec_...,v1=<hex-hmac>Call guard before doing any billable work. It verifies the timestamp and signature against the exact request bytes, then checks the requested allowance with ZeroClick. The SDK reads a clone, so your original request body remains available to the application.
import { zeroClick } from "./zeroclick.ts";
export async function POST(request: Request) {
const serviceSlug = "hello-api";
const usage = [{ meterSlug: "say_hello_requests", quantity: 1 }];
const decision = await zeroClick.guard(request, {
serviceSlug,
usage,
});
if (decision.action === "deny") return decision.response;
const result = { message: "Hello from Acme" };
return zeroClick.withUsage(
Response.json(result),
usage.map((item) => ({ serviceSlug, ...item })),
);
}Use this pattern on every endpoint that requires ZeroClick authentication or billing. A valid anonymous probe also reaches the allow/deny flow: its decision.context.zcAgentId is null until the buyer proves identity. Do not reject a request only because zc-agent-id is absent.
Frameworks with web-native route handlers can pass their Request directly. For Express, Fastify, and other Node-specific request types, adapt the request at the framework boundary before a body parser changes it. The adapted Request must retain the original method, full path and query, headers, and exact body bytes; never verify a re-serialized JSON body.
guard sends all meters needed for the action in one allowance check. Quantities must be positive integers, and a check must not contain the same meter slug twice.
{
"serviceSlug": "product-watch",
"usage": [
{ "meterSlug": "product_watch_hours", "quantity": 2 },
{ "meterSlug": "product_watch_checks", "quantity": 1 }
]
}When the signature is missing, stale, or invalid, the deny decision contains the SDK's 401 response and the allowance API is not called. When a valid request has insufficient allowance, the deny decision contains the exact seller 402 response ZeroClick expects:
{
"error": "payment_required",
"serviceSlug": "product-watch",
"planSlug": "growth",
"usage": [{ "meterSlug": "product_watch_hours", "quantity": 2 }]
}planSlug is optional in the guard input. Include it when the request should be priced against a specific plan, such as overage on a usage-based subscription. If you omit it for pay as you go usage, ZeroClick chooses the lowest priced pay as you go plan that can price the requested meters.
Use the same check for pay as you go, credit, subscriptions, and subscription overage. Your endpoint should simply check, reject with the schema above when needed, and allow the request when the check says it is allowed.
Allowance infrastructure failures happen only after the request signature verifies. Configure their behavior on createSeller:
"allow" is the default. guard returns an allow decision with allowance.status === "unavailable" after the timeout."deny" returns an SDK-provided 503 response."throw" throws a typed ZCError for your application to handle.Use onAllowanceUnavailable for operational logging. Never apply the fail-open policy to a missing or invalid signature.
For normal request and response APIs, withUsage reports usage synchronously by returning the validated zc-usage header with your successful response. It does not consume the response body.
return zeroClick.withUsage(
Response.json(result),
[
{
serviceSlug: "product-watch",
meterSlug: "product_watch_hours",
quantity: 2,
},
],
);ZeroClick records the usage and removes the zc-usage header before the response goes back to the agent.
For work that finishes later, use reportUsage. Persist the verified zcAgentId with the job and supply a stable idempotencyKey so retries do not double-record usage. The SDK does not invent idempotency keys or automatically retry billable work.
await zeroClick.reportUsage({
idempotencyKey: `job_${job.id}_final_tokens`,
zcAgentId: job.zcAgentId,
serviceSlug: "research-api",
meterSlug: "output_tokens",
quantity: 4200,
});The SDK validates public inputs and ZeroClick API responses at runtime. Malformed method arguments, transport failures, unsuccessful API responses, and invalid API responses throw ZCError instances. Use isZCError to narrow them without depending on error strings.
import { isZCError } from "@zeroclickai/sellers";
try {
await zeroClick.reportUsage(report);
} catch (error) {
if (isZCError(error, "api_status_error")) {
console.error("ZeroClick rejected the usage report", {
status: error.context.status,
reason: error.context.reason,
});
} else if (isZCError(error)) {
console.error("ZeroClick SDK operation failed", {
code: error.code,
operation: error.context.operation,
});
}
throw error;
}Error context is sanitized and does not include signing secrets, API keys, or request body bytes. Expected guard outcomes remain decisions: return decision.response for a deny instead of treating an invalid signature or allowance denial as an exception.
Connect Stripe in the dashboard before charging agents. ZeroClick creates payment requests for the buying agent and routes settlement to your connected Stripe account.
You do not need to invoice agents, collect cards, or handle wallet credentials in your API. Your server uses @zeroclickai/sellers to verify ZeroClick, check allowance, reject requests that need payment, and report usage.