Launch Rail Logo
Launch Rail
Documentation

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

launchrail-ctl — zsh
$
CLI SimulationActive

Pipeline Architecture

All submissions undergo automated validation, virus scanning (optional), and encryption before storage.

Entry Points
gRPC
MCP Agent
Service Logic
Domain Engine
Persistence
PostgreSQL
NATS Bus

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.

  1. Generate Upload URL (/v1/public/forms/uploads/generate): Get a presigned upload URL.
  2. Upload Bytes: Execute a PUT request with raw file bytes to the URL.
  3. 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: "..."