BETA
Skip to content

Forms

Create, retrieve, update, and delete forms.

Base URL: https://app.krafter.dev/api/v1

List Forms

Retrieve all forms for your team.

GET /forms

Required scope: forms:read

Example Request

bash
curl https://app.krafter.dev/api/v1/forms \
  -H "Authorization: Bearer kr_live_abc123def456"

Example Response

json
{
  "data": [
    {
      "id": "b1c2d3e4-5f6a-7b8c-9d0e-1f2a3b4c5d6e",
      "name": "Contact Form",
      "slug": "contact",
      "status": "active",
      "endpoint_url": "/f/contact",
      "fields": {},
      "settings": {},
      "notifications": {
        "email_enabled": true,
        "email_to": "team@yourcompany.com"
      },
      "submission_count": 42,
      "created_at": "2025-01-15T10:00:00Z",
      "updated_at": "2025-01-15T10:00:00Z"
    },
    {
      "id": "c2d3e4f5-6a7b-8c9d-0e1f-2a3b4c5d6e7f",
      "name": "Newsletter Signup",
      "slug": "newsletter",
      "status": "paused",
      "endpoint_url": "/f/newsletter",
      "fields": {},
      "settings": {},
      "notifications": {
        "email_enabled": false,
        "email_to": null
      },
      "submission_count": 128,
      "created_at": "2025-01-10T08:00:00Z",
      "updated_at": "2025-01-12T14:30:00Z"
    }
  ]
}

Create Form

Create a new form.

POST /forms

Required scope: forms:write

Request Body

FieldTypeRequiredDescription
namestringYesDisplay name for the form
slugstringYesURL-friendly identifier. Must be globally unique across all teams on the platform — the public submission endpoint /f/{slug} resolves slugs without a team scope, so two teams cannot share the same slug.
fieldsobjectNoField definitions for the form builder
settingsobjectNoForm settings (e.g., rate_limit)
notificationsobjectNoEmail notification settings
spam_protectionobjectNoSpam protection configuration
statusstringNoOne of "active", "paused", "archived". Defaults to "active". See Pausing a form.

Example Request

bash
curl -X POST https://app.krafter.dev/api/v1/forms \
  -H "Authorization: Bearer kr_live_abc123def456" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Contact Form",
    "slug": "contact",
    "notifications": {
      "email_enabled": true,
      "email_to": "team@yourcompany.com"
    },
    "settings": {
      "rate_limit": 10
    }
  }'

Example Response

json
// 201 Created
{
  "id": "b1c2d3e4-5f6a-7b8c-9d0e-1f2a3b4c5d6e",
  "name": "Contact Form",
  "slug": "contact",
  "status": "active",
  "endpoint_url": "/f/contact",
  "fields": {},
  "settings": {
    "rate_limit": 10
  },
  "notifications": {
    "email_enabled": true,
    "email_to": "team@yourcompany.com"
  },
  "submission_count": 0,
  "created_at": "2025-01-15T10:00:00Z",
  "updated_at": "2025-01-15T10:00:00Z"
}

WARNING

The slug must be globally unique across the platform, not just within your team. The public /f/{slug} endpoint resolves a form by slug without a team scope, so two teams cannot share the same slug. If the slug is already taken — by your team or by anyone else — the API returns 422 Unprocessable Entity with {"errors": {"slug": ["has already been taken"]}}.


Get Form

Retrieve details of a specific form, including its submission count.

GET /forms/:id

Required scope: forms:read

Example Request

bash
curl https://app.krafter.dev/api/v1/forms/b1c2d3e4-5f6a-7b8c-9d0e-1f2a3b4c5d6e \
  -H "Authorization: Bearer kr_live_abc123def456"

Example Response

json
{
  "id": "b1c2d3e4-5f6a-7b8c-9d0e-1f2a3b4c5d6e",
  "name": "Contact Form",
  "slug": "contact",
  "status": "active",
  "endpoint_url": "/f/contact",
  "fields": {},
  "settings": {
    "rate_limit": 10
  },
  "notifications": {
    "email_enabled": true,
    "email_to": "team@yourcompany.com"
  },
  "submission_count": 42,
  "created_at": "2025-01-15T10:00:00Z",
  "updated_at": "2025-01-15T10:00:00Z"
}

Update Form

Update an existing form. Only include the fields you want to change.

PATCH /forms/:id

Required scope: forms:write

Request Body

FieldTypeDescription
namestringDisplay name
slugstringURL slug. Must be globally unique across the platform (see the create-form note).
fieldsobjectField definitions
settingsobjectForm settings
notificationsobjectEmail notification settings
spam_protectionobjectSpam protection configuration
statusstringOne of "active", "paused", "archived". See Pausing a form.

Example Request

bash
curl -X PATCH https://app.krafter.dev/api/v1/forms/b1c2d3e4-5f6a-7b8c-9d0e-1f2a3b4c5d6e \
  -H "Authorization: Bearer kr_live_abc123def456" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Contact Us",
    "notifications": {
      "email_enabled": true,
      "email_to": "team@yourcompany.com,support@yourcompany.com"
    }
  }'

Example Response

json
{
  "id": "b1c2d3e4-5f6a-7b8c-9d0e-1f2a3b4c5d6e",
  "name": "Contact Us",
  "slug": "contact",
  "status": "active",
  "endpoint_url": "/f/contact",
  "fields": {},
  "settings": {
    "rate_limit": 10
  },
  "notifications": {
    "email_enabled": true,
    "email_to": "team@yourcompany.com,support@yourcompany.com"
  },
  "submission_count": 42,
  "created_at": "2025-01-15T10:00:00Z",
  "updated_at": "2025-01-16T09:15:00Z"
}

Delete Form

Permanently delete a form and all its submissions.

DELETE /forms/:id

Required scope: forms:write

Example Request

bash
curl -X DELETE https://app.krafter.dev/api/v1/forms/b1c2d3e4-5f6a-7b8c-9d0e-1f2a3b4c5d6e \
  -H "Authorization: Bearer kr_live_abc123def456"

Example Response

204 No Content

WARNING

This action is irreversible. All submissions, uploaded files, and webhooks associated with the form will be permanently deleted.


Pausing a form

A form's status field controls whether the public submission endpoint accepts new submissions. Set status: "paused" to temporarily reject submissions while keeping the form configuration and historical submissions intact. Set it back to "active" to resume.

StatusPublic POST /f/:slugVisible in dashboardListed by GET /forms
activeaccepted (201)yesyes
pausedrejected — 422 Unprocessable Entity {"error": "submission_failed"}yesyes
archivedrejected — 422 Unprocessable Entity {"error": "submission_failed"}yesyes

Paused/archived forms return the same 422 as a missing slug

The 422 response body for inactive forms is {"error": "submission_failed"} — identical to the response a real unknown slug gets. There is no separate form_inactive response: Forms.get_form_by_slug_global/1 filters status == "active" directly in the lookup query (lib/krafter/forms.ex:278), so any non-active form falls through the controller's nil branch. The submitter has no way to distinguish "slug doesn't exist" from "form is paused/archived" — both are deliberately indistinguishable to discourage slug enumeration.

Example: pause a form

bash
curl -X PATCH https://app.krafter.dev/api/v1/forms/b1c2d3e4-5f6a-7b8c-9d0e-1f2a3b4c5d6e \
  -H "Authorization: Bearer kr_live_abc123def456" \
  -H "Content-Type: application/json" \
  -d '{"status": "paused"}'
json
{
  "id": "b1c2d3e4-5f6a-7b8c-9d0e-1f2a3b4c5d6e",
  "name": "Contact Form",
  "slug": "contact",
  "status": "paused",
  "endpoint_url": "/f/contact"
}

Form Object

FieldTypeDescription
idstringUnique form identifier (UUID)
namestringDisplay name
slugstringURL slug used in the public endpoint
statusstringLifecycle status: "active", "paused", or "archived". Default "active".
endpoint_urlstringRelative path to the public submission endpoint (/f/{slug})
fieldsobjectField definitions configured via the form builder
settingsobjectForm settings (e.g., rate_limit)
notificationsobjectEmail notification configuration
submission_countintegerTotal number of submissions. Incremented atomically inside the same transaction as the submission insert; read-only via the API.
created_atstringISO 8601 creation timestamp
updated_atstringISO 8601 last update timestamp

Error Responses

422 Unprocessable Entity

Returned when validation fails (e.g., duplicate slug, missing required fields):

json
{
  "errors": {
    "slug": ["has already been taken"]
  }
}

404 Not Found

Returned when the form ID does not exist or does not belong to your team:

json
{
  "error": "Not found"
}

Built by Krafter Studio