Last Updated: 2026-06-28 Status: Active Service:
apps/forms-servicePublic submit URL:https://api.vlozi.app/forms/f/:form_id
What this is
Vlozi Forms is a multi-tenant, Web3Forms-grade form backend. A tenant creates a form in the dashboard, gets a public form_id, and embeds a <form> (or AJAX call, or SDK) on any website. The service filters spam, stores submissions, bills credits, notifies the owner via email, fires webhooks, and exposes analytics — all without any tenant-side backend code.
Doc Index
| File | What's in it |
|---|---|
| architecture.md | System diagram, three request surfaces, middleware pipeline, submission flow sequence, service boundaries, caching model, gateway wiring, environment bindings |
| database.md | All 5 tables with every column, indexes, JSONB shapes, migration history |
| api-spec.md | Every HTTP route — public submit, admin CRUD, MCP tools, analytics, webhooks, health endpoints |
| billing.md | Credit rate (0.25 cr/submission), accumulator model, settlement idempotency, fail-open behavior |
| permissions.md | Permission strings, full route → permission matrix, gateway headers, role bundles |
| embedding.md | Four embedding surfaces: hosted page, script embed, SDK, plain HTML; field types |
| webhooks.md | Setup, payload shape, headers, HMAC signing, retry policy, delivery log, known gaps |
| backlog.md | Bugs, gaps, improvement scope, scorecard |
| forms-vision.md | Product vision, three pillars, target personas, phased roadmap (Phases 2–5 deferred) |
| implementation-plan.md | Phase 1 completion checklist, deferred items, net-new vs reused summary |
Reading Paths
New dev getting started:
- architecture.md §1–4 — what the service is, three surfaces, middleware
- database.md — 5 tables in 10 minutes
- api-spec.md §3 — public submit flow (most complex handler)
- billing.md — accumulator model
Adding or changing a route:
- api-spec.md — where the route lives
- permissions.md — what permission to require
- billing.md if it touches credit charges
Debugging a submission problem:
- architecture.md §6 — full submission sequence diagram
- webhooks.md — if webhook delivery is suspect
- billing.md §2 — if it's a 402
Planning future work:
- backlog.md — bugs and gaps with priority
- forms-vision.md — phases 2–5 roadmap
Codebase Quick Map
apps/forms-service/
├── src/
│ ├── index.ts — Hono app, gateway guard, route mounts
│ ├── context.ts — RequestContext type
│ ├── db/
│ │ ├── client.ts — Neon connection + QueryLogger
│ │ └── schema.ts — 5 tables: forms, form_submissions,
│ │ form_webhook_deliveries, form_views_daily,
│ │ tenant_forms_usage
│ ├── middleware/
│ │ ├── auth.middleware.ts — requireTenant, requirePermission
│ │ └── db.middleware.ts — per-isolate DB cache
│ ├── routes/
│ │ ├── public/
│ │ │ └── submit.ts — POST /f/:id, GET /f/:id/schema,
│ │ │ GET /f/:id/page, GET /f/embed.js
│ │ ├── admin/
│ │ │ ├── forms.ts — CRUD + duplicate + analytics
│ │ │ ├── submissions.ts — inbox, export, reply
│ │ │ └── webhooks.ts — delivery log
│ │ └── mcp/
│ │ └── tools.ts — 15 tools mirroring admin REST
│ ├── services/
│ │ ├── form.utils.ts — ID gen, slug gen, uniqueness
│ │ ├── schema.ts — zodFromSchema() validator builder
│ │ ├── notify.ts — owner email via comms-service
│ │ ├── webhook.ts — deliverWebhook(), HMAC signing
│ │ ├── credits.ts — canSubmissionProceed(), meterSubmission()
│ │ └── analytics.ts — overview + per-form aggregation
│ ├── lib/
│ │ └── embed.ts — self-contained embed.js string
│ └── utils/
│ ├── errors.ts — jsonError, ErrorCode
│ └── logger.ts — structured logger
├── drizzle/
│ └── migrations/ — 5 SQL files (0000–0004)
├── test/ — 11 test files (vitest + PGLite)
└── wrangler.toml — bindings: COMMS_SERVICE, MEDIA_SERVICE,
FORM_RATE_LIMITER (20 req/60s)Four Things Every Developer Must Know
form_idis the only public credential. It's safe to embed in client HTML. Abuse is contained byallowed_origins(CORS), honeypot, Turnstile, and native rate limiting — not by a secret.- Submissions cost 0.25 credits. The credit gate runs before storage (fail-open on core DB outage). Spam submissions are never billed. See billing.md.
- All side effects run in
waitUntil(). Owner email, webhook delivery, and credit metering are non-blocking — they never delay the200to the visitor. A Worker crash after the response is sent will not roll back the submission. - Webhook secret is set once and cannot be rotated without clearing and resetting
webhookUrl. This is backlog.md B6.
Public Docs
User-facing documentation lives at logicspike/public-docs/08-forms/.