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();
// -> { "received": true }Submitting the same email again while a signup is still pending_review (or
already approved) is treated as a repeat submission when the server-side
de-dupe check observes the existing row, and the public response stays the same
neutral acknowledgement either way. It does not expose the pending signup id,
status, or creation timestamp, so it cannot be used to probe whether an email
has already signed up.
What protects this endpoint
The API validates the body strictly (unknown fields are rejected with 400),
performs best-effort de-dupe on email, and queues every fresh submission for 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 async) ──► 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