Skip to content

Forms API ​

Public API

The form config and submit endpoints are part of Studio's public, versioned API (/api/forms/v1/), designed to be called directly from your site. Submission management (review, approve, convert) is part of the internal application API.

Studio supports public form submissions that map to collection models. Forms can be embedded on external websites to collect data that flows into the content pipeline.

Public Endpoints ​

These endpoints require no authentication and are designed for external embedding.

Get Form Configuration ​

Get the public schema for a form-enabled model.

GET /api/forms/v1/:projectId/:modelId/config

Response ​

json
{
  "modelId": "contact-form",
  "fields": {
    "name": { "type": "string", "required": true },
    "email": { "type": "email", "required": true },
    "message": { "type": "text", "required": true },
    "newsletter": { "type": "boolean", "required": false }
  },
  "captcha": "turnstile",
  "successMessage": "Thank you for your submission!",
  "honeypotField": "_hp"
}

Only fields listed in the model's exposedFields configuration are returned. Internal fields are never exposed.

Example ​

bash
curl https://studio.example.com/api/forms/v1/{projectId}/{modelId}/config

CORS ​

The endpoint returns Access-Control-Allow-Origin: * headers for cross-origin embedding.


Submit Form ​

Submit data to a form-enabled model.

POST /api/forms/v1/:projectId/:modelId/submit

Request Body ​

FieldTypeRequiredDescription
dataobjectYesForm field values
captchaTokenstringConditionalTurnstile token (when captcha is configured)
_hpstringNoHoneypot field (must be empty -- bots fill it)

Response ​

Success:

json
{
  "success": true,
  "message": "Thank you for your submission!"
}

Validation Error:

json
{
  "success": false,
  "errors": [
    { "field": "email", "message": "email must be a valid email" }
  ]
}

Security Features ​

FeatureDescription
Rate limitingPer-IP sliding window (configurable, default 10/min)
HoneypotHidden field -- bots fill it, humans do not. Silent 200 rejection.
CaptchaCloudflare Turnstile verification (Starter+ plan)
HTML sanitizationAll string values stripped of HTML tags, JS patterns, entities
Field filteringOnly exposed fields are accepted; others are silently dropped
ValidationSchema-based validation against model field definitions
Monthly limitsAtomic check + insert prevents race conditions

Plan Limits ​

PlanForm ModelsSubmissions/Month
Free00
Starter1100
Pro153,000
EnterpriseUnlimitedUnlimited

Auto-Approve ​

When configured and the plan supports forms.auto_approve, submissions are automatically converted to content entries and merged into the repository.


Admin Endpoints ​

These endpoints require authentication and workspace membership.

List Submissions ​

GET /api/workspaces/:workspaceId/projects/:projectId/forms/:modelId/submissions

Query Parameters ​

ParameterTypeDefaultDescription
statusstringpendingFilter: pending, approved, rejected, spam
pagenumber1Page number
limitnumber20Items per page
sortstringnewestSort: newest, oldest

Response ​

json
{
  "submissions": [
    {
      "id": "uuid",
      "model_id": "contact-form",
      "data": { "name": "John", "email": "[email protected]", "message": "Hello" },
      "status": "pending",
      "source_ip": "192.168.1.1",
      "created_at": "2026-01-01T00:00:00Z"
    }
  ],
  "total": 15
}

Update Submission Status ​

PATCH /api/workspaces/:workspaceId/projects/:projectId/forms/:modelId/submissions/:submissionId
FieldTypeRequiredDescription
status'approved' | 'rejected' | 'spam'YesNew status

Delete Submission ​

DELETE /api/workspaces/:workspaceId/projects/:projectId/forms/:modelId/submissions/:submissionId

Bulk Update ​

POST /api/workspaces/:workspaceId/projects/:projectId/forms/:modelId/submissions/bulk
FieldTypeRequiredDescription
action'approve' | 'reject' | 'spam' | 'delete'YesImperative action to apply to each submission
submissionIdsstring[]YesSubmission IDs to act on (max 50 per request)

The field is action (imperative), not status. approve creates a content entry in Git for each submission; delete permanently removes them (with a GDPR audit log entry).

Released under the AGPL-3.0 License.