Submissions & Forms API
A platform-grade, multi-tenant SaaS Forms Runtime & Inbox Workflow module. Embed public forms safely and build sophisticated administrative back-office setups.
Environment Setup & Auth
The service runs natively with multi-tenant contexts out of the box.
Authentication Patterns
- 1. Admin API (Authenticated)
Used for managing forms, viewing submissions, and workflows. Requires a valid Bearer token and tenant context header (X-Company-ID). - 2. Public API (Edge)
Used by end-users submitting forms. Unauthenticated but strictly protected by CORS (allowed_domains), Rate Limits, and server-driven CAPTCHA.
CLI Interaction
Pipeline Architecture
All submissions undergo automated validation, virus scanning (optional), and encryption before storage.
Core Entity Hierarchy
Form
The high-level container. Defined by a slug and a strict list of permitted domains.
FormVersion
A specific iteration of a form's schema and UI properties. Only one version is active at any time.
Submission
The data captured from end-users, along with embedded metadata and state lifecycle tracking.
Thread
Internal/external notes attached to a submission for seamless back-office discussion.
Frontend: Embedding Public Forms Swagger UI
The Public Edge API is for untrusted clients. Identify the form using its public_key or routing slug.
1. Fetch Configuration (GetPublicForm)
Fetch schema and UI config before rendering (POST /v1/public/forms/lookup):
{
"public_key": "pk_live_123456789"
}2. Submitting Data (SubmitForm)
Send payload to /v1/public/forms/submissions. Always pass an idempotency_key generated client-side to prevent duplicates on un-stable connections.
{
"public_key": "4a826a1a-34b6-40d2-ad3b-845adc5181b9",
"idempotency_key": "uuid-v4-client-side",
"payload": {
"name": "Jane Smith",
"email": "jane@example.com"
},
"captcha_token": "<token from your CAPTCHA provider>"
}Handling File Uploads (Direct-to-Cloud)
If your form accepts files, you must upload them directly to cloud storage before finalizing the submission link.
- Generate Upload URL (
/v1/public/forms/uploads/generate): Get a presigned upload URL. - Upload Bytes: Execute a
PUTrequest with raw file bytes to the URL. - Finalize (
/v1/public/attachments/{id}/finalize): Mark successful attachment.
// 1. Generate presigned URL
const genRes = await fetch(`${BASE_URL}/v1/public/forms/uploads/generate`, {
method: 'POST',
body: JSON.stringify({
public_key: FORM_KEY,
submission_id: submissionId,
field_key: 'resume',
filename: file.name,
content_type: file.type,
size_bytes: file.size,
}),
});
const { attachment, upload_url } = await genRes.json();
// 2. Upload directly to S3/GCS
const uploadRes = await fetch(upload_url, {
method: 'PUT',
headers: { 'Content-Type': file.type },
body: file,
});
// 3. Finalize
await fetch(`${BASE_URL}/v1/public/attachments/${attachment.id}/finalize`, {
method: 'POST',
body: JSON.stringify({ attachment_id: attachment.id, public_key: FORM_KEY }),
});Frontend Admin: Building UIs Swagger UI
The Admin API requires a Bearer token and X-Company-ID for multi-tenant isolation.
Creating Forms (The Easy Way)
Use CreateFormSetup to create a form container + publish a version all in one request.
POST /v1/admin/forms/setup
{
"name": "B2B Contact Us",
"routing": {
"tenant_slug": "acme",
"form_slug": "contact-enterprise"
},
"allowed_domains": ["https://acme.com"],
"daily_submission_limit": 1000,
"json_schema": {
"type": "object",
"required": ["email"],
"properties": {
"email": { "type": "string" }
}
}
}Managing the Inbox
Use /v1/admin/submissions/search to power your dashboard lists with structured filters and full-text queries.
// Search for unassigned SPAM
{
"page": { "page_size": 25 },
"statuses": [ "SUBMISSION_STATUS_SPAM" ],
"assignee_id": ""
}Live Dashboard Metrics
Connect-Web enables server-streaming. Push live metrics to the dashboard without polling:
const client = createClient(FormsAdminService, transport);
for await (const response of client.watchInboxMetrics({ formId: formId })) {
updateDashboard(response.metrics);
}Exports & GDPR (DSAR)
For compliance tracing, erase all submissions linked to a specific user identity or email. This is irreversible.
POST /v1/admin/submissions/by-identity
Content-Type: application/json
X-Company-ID: caa10e22-...
{
"selector": { "email": "jane@example.com" }
}Backend Configuration & Operations Guide
This guide covers the technical setup, infra dependencies, and operational configuration required to run the Submissions & Forms API service. It is intended for Backend Engineers, DevOps, and Platform teams.
Infrastructure Dependencies
The service is designed to be stateless and horizontally scalable, relying on a few core infrastructure components defined in config.yaml.
1. PostgreSQL (Primary Database)
Used to store all persistent state: Forms, Versions, Submissions, Threads, Messages, and Audit Logs.
Config: database.url
2. Redis (Ephemeral State & Caching)
Crucial for high velocity and low-latency operations, rate limiting, and caching active Form Versions.
Config: redis.addr
3. Event Bus
Used for asynchronous processing and integrations (NATS, RabbitMQ, SQS, Google PubSub).
Config: event_bus.provider
4. Authorization Service (AuthZ)
The Admin API relies on an external gRPC service to enforce Role-Based Access Control (RBAC).
Config: authz_service.url
Rate Limiting
Rate limiting is natively built into the service to protect against abuse, backed by Redis. Two primary thresholds are configured per Form:
daily_submission_limit: Maximum successful submissions accepted per day.monthly_submission_limit: Maximum successful submissions accepted per month.
If the limit is exceeded, an HTTP 429 Too Many Requests error is returned.
Bot Detection & CAPTCHA
You must configure the global secrets for the providers you intend to support in config.yaml:
captcha:
recaptcha_v3:
secret_key: "YOUR_RECAPTCHA_SECRET_KEY"
score_threshold: 0.5
turnstile:
secret_key: "YOUR_TURNSTILE_SECRET_KEY"
hcaptcha:
secret_key: "YOUR_HCAPTCHA_SECRET_KEY"Configuration File Overview
port: "8080" # REST HTTP Port
grpc_port: "50051" # gRPC Port
environment: "development" # 'development' or 'production' (effects logging)
log_level: "info" # info, debug, error
database:
url: "postgres://user:password@localhost:5432/starter_db?sslmode=disable"
redis:
addr: "localhost:6379"
authz_service:
url: "localhost:50052"
telemetry:
collector_url: "localhost:4317"
service_name: "starter-kit"
audit:
url: "" # Optional external audit log service
event_bus:
provider: "nats"
nats:
url: "nats://localhost:4222"
captcha: # Global Captcha Secrets
recaptcha_v3:
secret_key: "..."
turnstile:
secret_key: "..."
hcaptcha:
secret_key: "..."