Skip to content

Forms

Dynamic form builder endpoints. Forms are created and managed through the admin, and rendered dynamically on the frontend based on their field structure. Submissions are stored in the database and can be reviewed in the admin.

Base path: /api/v1/forms


Public Endpoints

Get Form by Slug

Returns the full structure of an active form including fields and translations. No authentication required. Used by the frontend to render the form dynamically.

GET /api/v1/forms/public/{slug}

Path parameters

Parameter Type Description
slug string Unique form slug

Response 200 OK

{
  "id": "uuid",
  "slug": "contact",
  "is_active": true,
  "notify_emails": ["admin@site.com"],
  "created_by": "uuid",
  "translations": [
    {
      "id": "uuid",
      "form_id": "uuid",
      "language_id": "uuid",
      "name": "Contact Us",
      "description": "Send us a message and we will get back to you.",
      "success_message": "Thank you! We will be in touch soon.",
      "submit_label": "Send Message",
      "created_at": "2026-01-01T00:00:00Z",
      "updated_at": null
    }
  ],
  "fields": [
    {
      "id": "uuid",
      "form_id": "uuid",
      "name": "email",
      "type": "email",
      "is_required": true,
      "order": 0,
      "options": null,
      "translations": [
        {
          "id": "uuid",
          "field_id": "uuid",
          "language_id": "uuid",
          "label": "Email Address",
          "placeholder": "you@example.com",
          "help_text": null,
          "created_at": "2026-01-01T00:00:00Z",
          "updated_at": null
        }
      ],
      "created_at": "2026-01-01T00:00:00Z",
      "updated_at": null
    }
  ],
  "created_at": "2026-01-01T00:00:00Z",
  "updated_at": null
}

Errors

Status Description
404 Form not found or not active

Submit Form

Submits a filled form. Validates required fields and field types server-side. No authentication required.

POST /api/v1/forms/public/{slug}/submit

Path parameters

Parameter Type Description
slug string Unique form slug

Request body

{
  "data": {
    "email": "user@example.com",
    "name": "John Doe",
    "message": "Hello, I would like to get in touch."
  }
}

Field Type Required Description
data object Key-value pairs where keys match field name values

Response 201 Created

{
  "id": "uuid",
  "form_id": "uuid",
  "data": {
    "email": "user@example.com",
    "name": "John Doe",
    "message": "Hello, I would like to get in touch."
  },
  "ip_address": "192.168.1.1",
  "is_read": false,
  "created_at": "2026-01-01T00:00:00Z"
}

Server-side validation

The backend validates submissions against the form's field definitions:

  • Required fields must have a non-empty value
  • email fields must match a valid email format
  • number fields must be numeric
  • select and radio fields must contain a value defined in options

Errors

Status Description
400 Validation failed (required, type, etc)
404 Form not found or not active

Admin — Forms

List Forms

GET /api/v1/forms

Required permission: forms:read

Query parameters

Parameter Type Default Description
page int 1 Page number
per_page int 20 Results per page

Response 200 OK

{
  "data": [...],
  "pagination": {
    "total": 5,
    "page": 1,
    "per_page": 20,
    "pages": 1,
    "has_next": false,
    "has_prev": false
  }
}


Create Form

POST /api/v1/forms

Required permission: forms:create

Request body

{
  "slug": "contact",
  "is_active": true,
  "notify_emails": ["admin@site.com"],
  "translations": [
    {
      "language_id": "uuid",
      "name": "Contact Us",
      "description": "Send us a message.",
      "success_message": "Thank you!",
      "submit_label": "Send"
    }
  ],
  "fields": [
    {
      "name": "email",
      "type": "email",
      "is_required": true,
      "order": 0,
      "options": null,
      "translations": [
        {
          "language_id": "uuid",
          "label": "Email Address",
          "placeholder": "you@example.com",
          "help_text": null
        }
      ]
    }
  ]
}

Field Type Required Description
slug string 2–255 characters, unique
is_active boolean Default: true
notify_emails array List of email addresses for notifications
translations array Form translations
fields array Initial fields to create with the form

Response 201 Created — full FormResponse object.

Errors

Status Description
409 Slug already exists

Get Form

GET /api/v1/forms/{id}

Required permission: forms:read

Response 200 OK — full FormResponse object.

Errors

Status Description
404 Form not found

Update Form

PATCH /api/v1/forms/{id}

Required permission: forms:update

Request body — all fields optional

{
  "slug": "contact-us",
  "is_active": false,
  "notify_emails": ["admin@site.com", "support@site.com"]
}

Response 200 OK — updated FormResponse object.

Errors

Status Description
404 Form not found
409 Slug already exists

Delete Form

Soft-deletes a form and all its fields. Submissions are also removed.

DELETE /api/v1/forms/{id}

Required permission: forms:delete

Response 204 No Content

Errors

Status Description
404 Form not found

Admin — Form Translations

Upsert Form Translation

Creates or updates a translation for a specific language.

PUT /api/v1/forms/{id}/translations

Required permission: forms:update

Request body

{
  "language_id": "uuid",
  "name": "Contact Us",
  "description": "Send us a message.",
  "success_message": "Thank you! We will be in touch.",
  "submit_label": "Send Message"
}

Field Type Required Description
language_id UUID Must exist in DB
name string Form name shown in UI
description string Text shown above the form
success_message string Message shown after submission
submit_label string Text on the submit button

Response 200 OK — updated FormResponse object.


Delete Form Translation

DELETE /api/v1/forms/{id}/translations/{language_id}

Required permission: forms:update

Response 200 OK — updated FormResponse object.

Errors

Status Description
404 Form not found
404 Translation not found

Admin — Form Fields

Add Field

POST /api/v1/forms/{id}/fields

Required permission: forms:update

Request body

{
  "name": "phone",
  "type": "tel",
  "is_required": false,
  "order": 2,
  "options": null,
  "translations": [
    {
      "language_id": "uuid",
      "label": "Phone Number",
      "placeholder": "+381 11 123 456",
      "help_text": "Optional"
    }
  ]
}

Field Type Required Description
name string Technical key, unique per form (e.g. email, phone)
type string See field types below
is_required boolean Default: false
order int Display order, default: 0
options array Required for select, radio, checkbox types
translations array Field label, placeholder, help text per language

Response 201 Created — updated FormResponse object.

Errors

Status Description
404 Form not found
409 Field name already exists on this form

Update Field

PATCH /api/v1/forms/{id}/fields/{field_id}

Required permission: forms:update

Request body — all fields optional

Response 200 OK — updated FormResponse object.


Delete Field

DELETE /api/v1/forms/{id}/fields/{field_id}

Required permission: forms:update

Response 200 OK — updated FormResponse object.


Reorder Fields

Updates the display order of multiple fields in a single request. Used for drag-and-drop reordering in the admin UI.

PATCH /api/v1/forms/{id}/fields/reorder

Required permission: forms:update

Request body

{
  "fields": [
    { "field_id": "uuid-1", "order": 0 },
    { "field_id": "uuid-2", "order": 1 },
    { "field_id": "uuid-3", "order": 2 }
  ]
}

Response 200 OK — updated FormResponse object.

Errors

Status Description
404 One or more field IDs not found

Admin — Field Translations

Upsert Field Translation

PUT /api/v1/forms/{id}/fields/{field_id}/translations

Required permission: forms:update

Request body

{
  "language_id": "uuid",
  "label": "Email Address",
  "placeholder": "you@example.com",
  "help_text": "We will never share your email."
}

Field Type Required Description
language_id UUID Must exist in DB
label string Visible field label
placeholder string Input placeholder text
help_text string Helper text shown below the field

Response 200 OK — updated FormResponse object.


Delete Field Translation

DELETE /api/v1/forms/{id}/fields/{field_id}/translations/{language_id}

Required permission: forms:update

Response 200 OK — updated FormResponse object.


Admin — Submissions

List Submissions

GET /api/v1/forms/{id}/submissions

Required permission: forms:read

Query parameters

Parameter Type Default Description
page int 1 Page number
per_page int 20 Results per page
is_read boolean Filter by read status

Response 200 OK

{
  "data": [
    {
      "id": "uuid",
      "form_id": "uuid",
      "data": {
        "email": "user@example.com",
        "message": "Hello!"
      },
      "ip_address": "192.168.1.1",
      "is_read": false,
      "created_at": "2026-01-01T00:00:00Z"
    }
  ],
  "pagination": {
    "total": 10,
    "page": 1,
    "per_page": 20,
    "pages": 1,
    "has_next": false,
    "has_prev": false
  },
  "unread": 3
}

Unread count

The unread field always reflects the total number of unread submissions for this form, regardless of the current is_read filter. Use it to display a badge in the admin UI.


Mark Submission as Read

PATCH /api/v1/forms/{id}/submissions/{submission_id}/read

Required permission: forms:update

Response 200 OK — updated FormSubmissionResponse object.

Errors

Status Description
404 Submission not found

Delete Submission

DELETE /api/v1/forms/{id}/submissions/{submission_id}

Required permission: forms:delete

Response 204 No Content

Errors

Status Description
404 Submission not found

Field Types

Type Description options required
text Single-line text input
email Email input with format validation
tel Telephone number input
textarea Multi-line text input
number Numeric input with type validation
select Dropdown, single selection
radio Radio buttons, single selection
checkbox Checkbox group, multiple selection

Options format

For select, radio, and checkbox fields, options must be an array of objects:

[
  { "value": "general", "label": "General Inquiry" },
  { "value": "support", "label": "Technical Support" },
  { "value": "billing", "label": "Billing" }
]