Waitlist
Official Agent Skills for Zot — teach Claude Code, Cursor & other AI agents to integrate Zot waitlists, emails, and subscriptions correctly using the official SDK and CLI.
npx -y skills add launch-waitlist-zot/zot-skills --skill waitlistAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
One thing to look at
- 1 stars1 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
What its author says it does
Copied from the file, not written here
Integrate a Zot waitlist into any TypeScript, React, or Next.js app using the official `@zot-core/sdk` and the `@zot-core/cli` command-line tool. Use this skill whenever the user asks to "add a waitlist", "create a waitlist", "collect signups", "add early access", "integrate Zot", "build a coming soon page with email capture", or wires email capture to Zot.
The file declares its own license as MIT. That is the author’s claim about this one file, and it is not the same thing as the license GitHub reports for the repository, which is listed with the other numbers below.
SKILL.md
12.5 KB, as published. Nobody here has run it
Zot Waitlist - Best Practices
This skill tells you how to integrate a Zot waitlist correctly. Follow these rules. Do not invent alternatives.
TL;DR - the shortest path
# 1. Install the SDK
npm install @zot-core/sdk
# 2. Create the waitlist and persist its ID in .env.local
npx @zot-core/cli waitlist create --name "Early Access" --write-env .env.local --public
# 3. Build the UI (see Pattern B below) using `useAddUser` from "@zot-core/sdk/react"
That is the entire integration for a Next.js project. Do not add TanStack Query, do not write a custom POST handler, do not call the API with raw fetch. Everything you need is below.
Non-negotiable rules
- Use the official SDK. Package name:
@zot-core/sdk. Never callhttps://api.zot.sowith rawfetch,axios, or similar. - Pick the right entry point based on where the code runs:
- Server, API route, cron, script, edge function ->
import { ZotSDK } from "@zot-core/sdk" - React component that runs in the browser ->
import { useAddUser } from "@zot-core/sdk/react"
- Server, API route, cron, script, edge function ->
- Never wrap
useAddUserin TanStack Query, SWR,useState, Zustand, or a custom reducer. The hook already exposesisPending,isUserRegistered,data,error,isError, andreset. Reusing its state is the whole point. - Never ship a server-side API key to the client. For browser code use a key with signup-only permissions and expose it as
NEXT_PUBLIC_ZOT_API_KEY(or your framework's public-env equivalent). - Always handle
ZotAPIError. Show user-friendly copy for 409 (already registered) and 429 (rate limit).
Install
npm install @zot-core/sdk
# or
pnpm add @zot-core/sdk
# or
yarn add @zot-core/sdk
No extra peer deps are required for server usage. For the hook, react >= 18 must already be installed (the SDK marks it as optional peer).
Get your credentials
You need two things:
- API key - the fastest path is
npx @zot-core/cli login, which authenticates the user and writes a freshZOT_API_KEYinto the local env file. Alternatively, generate one manually from the Zot dashboard at https://app.zot.so/app/api-keys. Create one key for server usage and, if you plan to call from the browser, a second key scoped to signup-only. - Waitlist ID - looks like
wl_abc123.
# Authenticate and persist ZOT_API_KEY to .env.local (or .env if you pass --env .env)
npx @zot-core/cli login
Create the waitlist with @zot-core/cli (recommended)
If the user does not already have a waitlist, use the official Zot CLI. It calls the Zot API with the user's ZOT_API_KEY and writes the resulting ID directly into the env file so you never have to copy-paste it.
Package:
@zot-core/cli. Do not confuse it with@zot-core/agents-@zot-core/agentsonly installs this agent guide into a repo;@zot-core/clitalks to the Zot API.
# Make sure ZOT_API_KEY is set (in the shell, in .env.local, or in .env)
npx @zot-core/cli waitlist create \
--name "Early Access" \
--write-env .env.local \
--public
What this does:
- Calls
POST /v1/wait-listvia@zot-core/sdkunder the hood. - Prints the new waitlist (
_id,name, flags, timestamps). - Appends
ZOT_WAITLIST_ID=wl_...to.env.local. - Because
--publicwas passed, it also appendsNEXT_PUBLIC_ZOT_WAITLIST_ID=wl_...for client-side usage in Next.js.
Useful variations:
# Pure server stack (Node/Nest/Express). Drop --public.
npx @zot-core/cli waitlist create --name "Beta list" --write-env .env
# Non-interactive / scripted use. Prints JSON only.
npx @zot-core/cli waitlist create --name "Beta list" --api-key $ZOT_API_KEY --json
# Disable the automatic welcome email.
npx @zot-core/cli waitlist create --name "Quiet list" --no-send-email
If the CLI exits with No API key found, run npx @zot-core/cli login to authenticate and write ZOT_API_KEY into the env file, or grab one from https://app.zot.so/app/api-keys and export it in the shell / add it to .env.local / .env before retrying.
Alternative: create it from code (only if you have a clear reason)
import { ZotSDK } from "@zot-core/sdk";
const zot = new ZotSDK({ apiKey: process.env.ZOT_API_KEY! });
const waitlist = await zot.waitlists.create({
name: "Early Access",
sendEmailToNewSignup: true,
});
console.log(waitlist._id); // <- save this as ZOT_WAITLIST_ID
Only do this if the user explicitly wants creation flow inside their own app (e.g. admin UI that creates new waitlists per tenant). For a one-off project setup, always prefer the CLI.
Environment variables
Pick the pattern that matches the runtime.
Pure server / Node / backend
# .env
ZOT_API_KEY=zot_xxx
ZOT_WAITLIST_ID=wl_abc123
Next.js with the React hook on the client
# .env.local
NEXT_PUBLIC_ZOT_API_KEY=zot_xxx_signup_only
NEXT_PUBLIC_ZOT_WAITLIST_ID=wl_abc123
Use the NEXT_PUBLIC_ key only for the signup-scoped key. Keep the full-access key server-side under ZOT_API_KEY.
Pattern A - Server / API route
Use when the form submits to your backend (Next.js Server Action, API route, Express handler, Nest controller, etc.).
// app/api/waitlist/route.ts (Next.js App Router)
import { NextResponse } from "next/server";
import { ZotSDK, ZotAPIError } from "@zot-core/sdk";
const zot = new ZotSDK({ apiKey: process.env.ZOT_API_KEY! });
export async function POST(request: Request) {
const { email, name } = await request.json();
try {
const user = await zot.waitlist(process.env.ZOT_WAITLIST_ID!).addUser({
email,
name,
});
return NextResponse.json({ ok: true, user });
} catch (err) {
if (err instanceof ZotAPIError) {
if (err.statusCode === 409) {
return NextResponse.json(
{ ok: false, reason: "already_registered" },
{ status: 409 },
);
}
return NextResponse.json(
{ ok: false, reason: "zot_error", details: err.body },
{ status: err.statusCode },
);
}
return NextResponse.json({ ok: false }, { status: 500 });
}
}
Pattern B - Next.js client component with useAddUser (recommended for landing pages)
This is the canonical way when the form lives in a React component. The hook persists the "already registered" state in localStorage keyed by waitlistId, so repeat visitors see the confirmation without a server round-trip.
In Zot dashboard, a waitlist is considered "connected" after the first successful signup reaches the registration endpoint. If the user has a connection guide banner, it should disappear after a real successful submit.
"use client";
import { useState } from "react";
import { useAddUser } from "@zot-core/sdk/react";
export function WaitlistForm() {
const [email, setEmail] = useState("");
const [name, setName] = useState("");
const { addUser, isPending, isUserRegistered, error, reset } = useAddUser({
apiKey: process.env.NEXT_PUBLIC_ZOT_API_KEY!,
waitlistId: process.env.NEXT_PUBLIC_ZOT_WAITLIST_ID!,
onSuccess: () => {
// Optional: analytics, toast, redirect, etc.
},
});
if (isUserRegistered) {
return (
<div>
<p>You are on the list. We will email you soon.</p>
<button type="button" onClick={reset}>
Register another email
</button>
</div>
);
}
return (
<form
onSubmit={(event) => {
event.preventDefault();
addUser({ email, name: name || undefined });
}}
>
<input
type="email"
value={email}
onChange={(event) => setEmail(event.target.value)}
placeholder="[email protected]"
required
disabled={isPending}
/>
<input
type="text"
value={name}
onChange={(event) => setName(event.target.value)}
placeholder="Your name (optional)"
disabled={isPending}
/>
<button type="submit" disabled={isPending}>
{isPending ? "Joining..." : "Join the waitlist"}
</button>
{error && (
<p role="alert">
{"statusCode" in error && (error as { statusCode: number }).statusCode === 409
? "This email is already on the list."
: error.message}
</p>
)}
</form>
);
}
Full API surface (only what you need)
Server-side client
import { ZotSDK } from "@zot-core/sdk";
const zot = new ZotSDK({ apiKey: "zot_xxx" });
| Call | Purpose |
|---|---|
zot.waitlists.create(params) | Create a waitlist. |
zot.waitlists.list() | List waitlists for the API key. |
zot.waitlist(id).get() | Fetch one waitlist. |
zot.waitlist(id).update(params) | Update settings. |
zot.waitlist(id).delete() | Delete permanently. |
zot.waitlist(id).stats() | Aggregated stats. |
zot.waitlist(id).addUser({ email, name?, referredBy?, source?, metadata? }) | Register a user. |
zot.waitlist(id).listUsers() | List users. |
zot.waitlist(id).userCount() | { total, referred }. |
zot.waitlist(id).searchUser(email) | Find a user. |
zot.waitlist(id).updateUserStatus({ email, status }) | Status is "waiting" | "invited" | "converted" | "churned". |
zot.waitlist(id).bulkDeleteUsers(emails) | Accepts a single email or an array. |
zot.waitlist(id).blockedUsers() / blockedUserCount() | Blocked users. |
zot.waitlist(id).webhookEvents() | Webhook delivery history. |
React hook
import { useAddUser } from "@zot-core/sdk/react";
const { addUser, data, error, isPending, isUserRegistered, isError, reset } = useAddUser({
apiKey: "...",
waitlistId: "wl_...",
onSuccess: (user) => {},
onError: (err) => {},
});
Everything the hook returns is listed above. Do not create parallel state for these values.
Error codes
| Code | Meaning | UX guidance |
|---|---|---|
| 400 | Validation error | Show field-level error. |
| 401 | Bad/missing API key | Never shown to end users, log it. |
| 404 | Waitlist not found | Config issue, log it. |
| 409 | Email already registered | Treat as success in most landing pages. |
| 429 | Rate limited | Ask the user to try again in a minute. |
| 5xx | Server issue | Retry once, then ask the user to try later. |
Detect with err instanceof ZotAPIError and read err.statusCode and err.body.
Anti-patterns - do NOT do these
- Raw
fetch("https://api.zot.so/v1/wait-list/..."). Use the SDK. - Wrapping
useAddUserinuseMutationfrom TanStack Query. Redundant. - Adding a manual
useState<boolean>for loading. UseisPending. - Storing "already registered" in your own localStorage key.
useAddUseralready does it underzot:waitlist:registered:<waitlistId>. - Using the full-access
ZOT_API_KEYinside a"use client"component. - Calling
zot.waitlists.createon every render, build, or app startup. Usenpx @zot-core/cli waitlist createonce at setup time and store the ID. - Hard-coding the waitlist ID inside the component. Read it from env (
ZOT_WAITLIST_IDorNEXT_PUBLIC_ZOT_WAITLIST_ID). - Silently swallowing errors. 409 is a product decision; others should be surfaced or logged.
Verification checklist
Before telling the user the integration is done, confirm ALL of the following:
-
@zot-core/sdkis inpackage.jsonat the latest published version. - A waitlist exists and its ID is stored in env (created via
npx @zot-core/cli waitlist createor, only if justified, via a one-time script). - API key is in env vars, not hard-coded.
- If the key is used in client code, it is prefixed with
NEXT_PUBLIC_(or equivalent) AND is a signup-scoped key. - The waitlist ID is read from env, not hard-coded, unless the user explicitly asked for a hard-coded constant.
- The submit handler catches errors and the UI renders something for the error case.
- Success state is handled (either the hook's
isUserRegisteredor your own redirect/toast afteronSuccess). - At least one real signup was tested successfully (this is what marks the waitlist as connected in dashboard).
- No raw
fetchtoapi.zot.soanywhere. - No duplicated loading/error/success state around
useAddUser.
If any box is unchecked, fix it before handing the task back to the user.