Self-serve signup
A prospective merchant signs up by POSTing to one public endpoint. The request creates a pending signup that's reviewed (by rules, an operator, or both) and, once approved, provisions a tenant. This is the same endpoint the marketing site's signup form posts to — you can call it from your own front end too.
Submit a signup
POST /api/v1/public/signup — no auth, no tenant header (you're creating
a tenant, not acting within one).
const res = await fetch("https://api.litecommerce.io/api/v1/public/signup", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
contactName: "Dana Reyes", // required, 1–120 chars
email: "dana@summitgear.co", // required, valid email, ≤254 chars
tenantName: "Summit Gear Co.", // required, 2–120 chars
plan: "free", // optional: "free" | "pro" | "enterprise" (default "free")
source: "pricing-free", // optional attribution, ≤120 chars
}),
});
const signup = await res.json();
// → { "id": "…", "status": "pending_review", "createdAt": "2026-05-28T…Z" }Submitting the same email again while a signup is still pending_review (or
already approved) returns the existing record rather than creating a
duplicate — the endpoint is idempotent on email.
What protects this endpoint
The API validates the body strictly (unknown fields are rejected with 400),
de-dupes on email, and runs every submission through an auto-approval rule
set (below). Bot-defenses like Cloudflare Turnstile and a honeypot live in
the marketing site's hosted form, not the API itself — if you build your
own signup UI, add your own front-end abuse protection on top of the API's
rule checks.
The lifecycle
POST /public/signup
│
▼
pending_review ──► (auto-approval rules evaluate) ──► decision recorded
│ │
│ ┌─────────┴──────────┐
▼ ▼ ▼
operator review auto_approved flagged_for_review
in platform-admin (provisions when / enterprise_review
│ the plan's flag is on) │
▼ │ ▼
approved ──────────────────────────────────────┴──────────► operator decides
│
▼
tenant provisioned + welcome email
A pending signup carries two independent fields:
status—pending_review→approved·rejected·spam(terminal, set once).autoApprovalDecision—awaiting_evaluation→auto_approved·flagged_for_review·enterprise_review. This is the rules' verdict; it's separate from whether the tenant has actually been provisioned.
Auto-approval rules
Every signup is scored against a small rule set. Any failure routes it to
flagged_for_review (an operator decides); passing all of them yields
auto_approved. The checks:
- Disposable email domain — the address's domain is on a deny-list.
- Brand-impersonation tenant name — the name collides with a well-known brand.
- Per-IP rate — too many signups from one IP in a 24-hour window.
- Prior-tenant email — the email is already attached to a tenant or another pending signup.
- MX record — the email domain has no reachable mail server (DNS lookup, short timeout). A transient DNS failure also flags for review — but a signup flagged only for this reason is automatically re-evaluated after a short grace period, so a momentary blip recovers on its own; a genuinely unreachable domain stays flagged for an operator.
Enterprise signups skip the rules entirely and go straight to
enterprise_review — Enterprise is always a human conversation.
When does auto-approval actually provision?
A clean auto_approved verdict only provisions automatically when the
operator has enabled it for that plan via an Edge Config flag:
marketing_signupAutoApproveFreemarketing_signupAutoApprovePromarketing_signupAutoApproveEnterprise— code-enforced off. The API short-circuits Enterprise to "not auto-approved" before even reading the flag, so flipping it has no effect.
With a plan's flag off (the default — "Wave A"), an auto_approved
signup still waits for an operator to approve it manually. With it on
(Wave B/C), the system provisions the tenant within seconds of signup. Either
way the verdict is recorded for audit.
Flag keys use underscores, not dots — Vercel Edge Config rejects dotted keys.
What provisioning creates
On approval (operator-driven or automatic), one atomic transaction:
- Creates the organization on
FREE_TRIAL, statusONBOARDING. - Upserts the founding owner user (by email) and an
OWNERmembership. - Marks the pending signup
approvedand links it to the new org. - Queues a founding-owner welcome email (delivered via the outbox just after commit).
If anything in that transaction fails, it all rolls back — there's no half-provisioned tenant. The welcome email is a durable outbox event, so a delivery hiccup is retried rather than lost.
Related
- Plans — the
FREE_TRIALa new tenant lands on - Settings & onboarding — the first-run checklist the new owner sees
- Tenants & context — what the provisioned tenant scopes