ReferenceReference by module

Forms API

Every Forms action (7) and data model (3): endpoints, scopes, input, preconditions, events and examples.

Public forms anyone can fill in; answers arrive as leads or Base records.

Module code forms · 7 actions · 3 data models · 8 events. A key or token never does more here than the person behind it can; row-level security applies exactly as in the app. Scopes: forms (everything), forms:read (read only) or forms:<capability>.

Resources

ResourceTableList and readPOSTPATCHDELETE
form_submissionsform_submission/api/v1/form_submissions
public_form_fieldspublic_form_field/api/v1/public_form_fields
public_formspublic_form/api/v1/public_forms

Lists take limit (up to 200), cursor, order (e.g. -created_at), fields and q; every other query parameter is a column filter: ?status=paid, ?total[gte]=100, ?id[in]=a,b, ?x=null.

Actions

ActionNeedsWhat it does
forms.close_formforms:manageStop a form answering
forms.discard_submissionforms:manageDiscard a failed submission
forms.export_submissionsforms:exportExport submissions
forms.record_retryforms:manageRecord a retried submission
forms.save_formforms:manageCreate or edit a form
forms.set_fieldsforms:manageSet a form’s fields
forms.submitforms:writeSend a form from the website

forms.close_form

Stop a form answering — The address keeps working and tells visitors the form is closed.

Needs forms:manage · reaches outside (sends, publishes or charges) · asks a person to confirm when Era runs it

POSThttps://app.getera.io/api/v1/public_forms/{id}/actions/close_form
API key or OAuth token · forms:manage
  • Canonical address: POST /api/v1/actions/forms.close_form

Input

FieldTypeRequiredNotes
iduuidyesFilled from the path {id} · Form · id of public_form

Preconditions

The action is refused with 409 and one of these codes:

CodeRefused when
form_not_openIt must be answering to be closed.

Events it publishes: forms.form_closed

What it writes: public_form (update) — Stops it answering

curl -X POST https://app.getera.io/api/v1/public_forms/PUBLIC_FORM_ID/actions/close_form \
  -H "Authorization: Bearer $ERA_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{}'
Response
{
  "ok": true,
  "result": "…",
  "events": [
    "forms.form_closed"
  ]
}
import { createEra } from "@getera/sdk"

const era = createEra({ apiKey: process.env.ERA_API_KEY })
const res = await era.publicForms.closeForm("PUBLIC_FORM_ID")
era public_forms close_form PUBLIC_FORM_ID
tools/call
{
  "name": "era__forms__close_form",
  "arguments": {
    "id": "PUBLIC_FORM_ID"
  }
}

Errors: 401 no or expired credential · 403 the credential does not hold forms:manage · 404 the record or module is outside what it may reach · 422 invalid input (problems[]) · 409 form_not_open · 429 slow down

forms.discard_submission

Discard a failed submission — Keeps it in the log, out of the failed list. Nothing is created from it.

Needs forms:manage · reaches outside (sends, publishes or charges) · asks a person to confirm when Era runs it

POSThttps://app.getera.io/api/v1/form_submissions/{id}/actions/discard_submission
API key or OAuth token · forms:manage
  • Canonical address: POST /api/v1/actions/forms.discard_submission

Input

FieldTypeRequiredNotes
iduuidyesFilled from the path {id} · Submission · id of form_submission

Preconditions

The action is refused with 409 and one of these codes:

CodeRefused when
submission_not_foundThe submission must exist.
submission_not_failedOnly a failed submission is discarded.

Events it publishes: forms.submission_discarded

What it writes: form_submission (update) — Marks it discarded

curl -X POST https://app.getera.io/api/v1/form_submissions/FORM_SUBMISSION_ID/actions/discard_submission \
  -H "Authorization: Bearer $ERA_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{}'
Response
{
  "ok": true,
  "result": "…",
  "events": [
    "forms.submission_discarded"
  ]
}
import { createEra } from "@getera/sdk"

const era = createEra({ apiKey: process.env.ERA_API_KEY })
const res = await era.formSubmissions.discardSubmission("FORM_SUBMISSION_ID")
era form_submissions discard_submission FORM_SUBMISSION_ID
tools/call
{
  "name": "era__forms__discard_submission",
  "arguments": {
    "id": "FORM_SUBMISSION_ID"
  }
}

Errors: 401 no or expired credential · 403 the credential does not hold forms:manage · 404 the record or module is outside what it may reach · 422 invalid input (problems[]) · 409 submission_not_found, submission_not_failed · 429 slow down

forms.export_submissions

Export submissions — Every submission of a form as a table: answers by question, status, record and consent.

Needs forms:export · reaches outside (sends, publishes or charges)

POSThttps://app.getera.io/api/v1/actions/forms.export_submissions
API key or OAuth token · forms:export

Input

FieldTypeRequiredNotes
formIduuidyesForm · id of public_form
statusstring (enum)Only · Allowed values: accepted, failed, spam, discarded

Preconditions

The action is refused with 409 and one of these codes:

CodeRefused when
form_not_foundThe form must exist.

Events it publishes: forms.submissions_exported

curl -X POST https://app.getera.io/api/v1/actions/forms.export_submissions \
  -H "Authorization: Bearer $ERA_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{"formId":"PUBLIC_FORM_ID"}'
Response
{
  "ok": true,
  "result": "…",
  "events": [
    "forms.submissions_exported"
  ]
}
import { createEra } from "@getera/sdk"

const era = createEra({ apiKey: process.env.ERA_API_KEY })
const res = await era.actions.run("forms.export_submissions", {
  formId: "PUBLIC_FORM_ID"
})
era forms export_submissions \
  --form-id "PUBLIC_FORM_ID"
tools/call
{
  "name": "era__forms__export_submissions",
  "arguments": {
    "formId": "PUBLIC_FORM_ID"
  }
}

Errors: 401 no or expired credential · 403 the credential does not hold forms:export · 404 the record or module is outside what it may reach · 422 invalid input (problems[]) · 409 form_not_found · 429 slow down

forms.record_retry

Record a retried submission — Marks a failed submission accepted with the record it became, or failed again.

Needs forms:manage · changes data

POSThttps://app.getera.io/api/v1/form_submissions/{id}/actions/record_retry
API key or OAuth token · forms:manage
  • Canonical address: POST /api/v1/actions/forms.record_retry

Input

FieldTypeRequiredNotes
iduuidyesFilled from the path {id} · Submission · id of form_submission
recordTypestringRecord type · max 40
recordIduuidRecord
errorstringWhy it failed · max 2000

Preconditions

The action is refused with 409 and one of these codes:

CodeRefused when
submission_not_foundThe submission must exist.
submission_not_failedOnly a failed submission is retried.

Events it publishes: forms.submission_recovered, forms.submission_failed

What it writes: form_submission (update) — Writes the retry outcome

curl -X POST https://app.getera.io/api/v1/form_submissions/FORM_SUBMISSION_ID/actions/record_retry \
  -H "Authorization: Bearer $ERA_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{"recordType":"Record type"}'
Response
{
  "ok": true,
  "result": "…",
  "events": [
    "forms.submission_recovered",
    "forms.submission_failed"
  ]
}
import { createEra } from "@getera/sdk"

const era = createEra({ apiKey: process.env.ERA_API_KEY })
const res = await era.formSubmissions.recordRetry("FORM_SUBMISSION_ID", {
  recordType: "Record type"
})
era form_submissions record_retry FORM_SUBMISSION_ID \
  --record-type "Record type"
tools/call
{
  "name": "era__forms__record_retry",
  "arguments": {
    "id": "FORM_SUBMISSION_ID",
    "recordType": "Record type"
  }
}

Errors: 401 no or expired credential · 403 the credential does not hold forms:manage · 404 the record or module is outside what it may reach · 422 invalid input (problems[]) · 409 submission_not_found, submission_not_failed · 429 slow down

forms.save_form

Create or edit a form — Names a public form, decides what it creates and whether it is answering.

Needs forms:manage · changes data

POSThttps://app.getera.io/api/v1/public_forms/{id}/actions/save_form
API key or OAuth token · forms:manage
  • Canonical address: POST /api/v1/actions/forms.save_form

Input

FieldTypeRequiredNotes
iduuidFilled from the path {id} · Form · id of public_form
slugstringyesAddress · max 80
titlestringyesTitle · max 200
kindstringCreates · max 32
dataTableIduuidTable · id of data_table
savedViewIduuidForm view · id of saved_view
descriptionstringDescription
successMessagestringThank-you message
consentTextstringConsent the visitor must give · max 2000
redirectUrlstringSend them to · max 2000
settingsobject / arrayBehaviour
isActivebooleanAnswering

Preconditions

The action is refused with 409 and one of these codes:

CodeRefused when
invalid_slugThe public address must be a slug.
slug_takenTwo forms may not answer on one address.
unknown_form_kindA form must create something this module knows.
target_module_offA lead form needs Sales switched on.
form_table_not_foundA table form needs a Base table.
view_not_formA form is opened from a Form view of its own table.
invalid_redirectA redirect is an http or https address.

Events it publishes: forms.form_saved

What it writes: public_form (upsert) — Writes the form

curl -X POST https://app.getera.io/api/v1/public_forms/PUBLIC_FORM_ID/actions/save_form \
  -H "Authorization: Bearer $ERA_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{"slug":"Address","title":"Title"}'
Response
{
  "ok": true,
  "result": "…",
  "events": [
    "forms.form_saved"
  ]
}
import { createEra } from "@getera/sdk"

const era = createEra({ apiKey: process.env.ERA_API_KEY })
const res = await era.publicForms.saveForm("PUBLIC_FORM_ID", {
  slug: "Address",
  title: "Title"
})
era public_forms save_form PUBLIC_FORM_ID \
  --slug "Address" \
  --title "Title"
tools/call
{
  "name": "era__forms__save_form",
  "arguments": {
    "id": "PUBLIC_FORM_ID",
    "slug": "Address",
    "title": "Title"
  }
}

Errors: 401 no or expired credential · 403 the credential does not hold forms:manage · 404 the record or module is outside what it may reach · 422 invalid input (problems[]) · 409 invalid_slug, slug_taken, unknown_form_kind, target_module_off, form_table_not_found, view_not_form, invalid_redirect · 429 slow down

forms.set_fields

Set a form’s fields — Replaces the whole field list, in the order they will be shown, with their rules.

Needs forms:manage · removes or cannot be undone

POSThttps://app.getera.io/api/v1/public_form_fields/actions/set_fields
API key or OAuth token · forms:manage
  • Canonical address: POST /api/v1/actions/forms.set_fields

Input

FieldTypeRequiredNotes
formIduuidyesForm · id of public_form
fieldsobject / arrayyesFields

Preconditions

The action is refused with 409 and one of these codes:

CodeRefused when
form_not_foundThe form must exist.
invalid_fieldsEvery field needs a label and a target.
duplicate_targetTwo fields may not write the same thing.
unknown_targetA field must write something that exists.
type_not_submittableA public form cannot ask a stranger for a file, a reference or raw JSON.
invalid_visibilityA field is shown depending only on questions above it.
invalid_validationLengths and bounds are numbers, a pattern is a regular expression.

Events it publishes: forms.fields_changed

What it writes: public_form_field (delete) — Clears the old list; public_form_field (insert) — Writes the new one, in order

curl -X POST https://app.getera.io/api/v1/public_form_fields/actions/set_fields \
  -H "Authorization: Bearer $ERA_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{"formId":"PUBLIC_FORM_ID","fields":{}}'
Response
{
  "ok": true,
  "result": "…",
  "events": [
    "forms.fields_changed"
  ]
}
import { createEra } from "@getera/sdk"

const era = createEra({ apiKey: process.env.ERA_API_KEY })
const res = await era.publicFormFields.setFields({
  formId: "PUBLIC_FORM_ID",
  fields: {}
})
era public_form_fields set_fields --data '{"formId":"PUBLIC_FORM_ID","fields":{}}'
tools/call
{
  "name": "era__forms__set_fields",
  "arguments": {
    "formId": "PUBLIC_FORM_ID",
    "fields": {}
  }
}

Errors: 401 no or expired credential · 403 the credential does not hold forms:manage · 404 the record or module is outside what it may reach · 422 invalid input (problems[]) · 409 form_not_found, invalid_fields, duplicate_target, unknown_target, type_not_submittable, invalid_visibility, invalid_validation · 429 slow down

forms.submit

Send a form from the website — A visitor fills in one of your forms on your site.

Needs forms:write · reaches outside (sends, publishes or charges) · Also callable from a published site (visitor)

A website action: your site calls it through the site API (/api/site/v1/{siteId}, e.g. with @getera/storefront). With an API key or a token it is refused (403 site_only).
POSThttps://app.getera.io/api/v1/form_submissions/actions/submit
API key or OAuth token · forms:write
  • Canonical address: POST /api/v1/actions/forms.submit

Input

FieldTypeRequiredNotes
slugstringyesForm · max 80
answersobject / arrayAnswers
metaobject / arrayForm details

Preconditions

The action is refused with 409 and one of these codes:

CodeRefused when
form_not_answeringThe form must be open and belong to this site’s business.
form_writes_tableA table form is written by its own door.
answers_invalidRequired and valid answers.
consent_requiredThe consent box, when the form asks for it.
too_many_submissionsOne address may only send so often.

Events it publishes: forms.submitted

What it writes: form_submission (insert) — Logs the answers; a lead form also creates its lead

Errors: 401 no or expired credential · 403 the credential does not hold forms:write · 404 the record or module is outside what it may reach · 422 invalid input (problems[]) · 409 form_not_answering, form_writes_table, answers_invalid, consent_required, too_many_submissions · 429 slow down

Data models

FormSubmission

Table form_submission · Resource form_submissions

  • GET /api/v1/form_submissions
  • GET /api/v1/form_submissions/{id}
ColumnTypeNullableAllowed valuesReferences
idstring (uuid) · uuid
organization_idstring (uuid) · uuidorganization
workspace_idstring (uuid) · uuidworkspace
form_idstring (uuid) · uuidpublic_form
share_idstring (uuid) · uuidyes
statusstring · textaccepted, failed, spam, discarded
answersobject · jsonb
record_typestring · textyes
record_idstring (uuid) · uuidyes
errorstring · textyes
consent_atstring (date-time) · timestamptzyes
consent_textstring · textyes
attemptsinteger · int4
resolved_bystring (uuid) · uuidyesapp_user
resolved_atstring (date-time) · timestamptzyes
created_atstring (date-time) · timestamptz
updated_atstring (date-time) · timestamptz

Primary key: id

Checks:

  • CHECK ((jsonb_typeof(answers) = 'object'::text))
  • CHECK (((status <> 'accepted'::text) OR (record_id IS NOT NULL)))

PublicFormField

Table public_form_field · Resource public_form_fields

  • GET /api/v1/public_form_fields
  • GET /api/v1/public_form_fields/{id}
ColumnTypeNullableAllowed valuesReferences
idstring (uuid) · uuid
organization_idstring (uuid) · uuidorganization
workspace_idstring (uuid) · uuidworkspace
form_idstring (uuid) · uuidpublic_form
labelstring · text
helpstring · textyes
targetstring · textrecord, custom, column
target_keystring · text
is_requiredboolean · bool
col_spaninteger · int2
sectionstring · textyes
display_orderinteger · int4
created_atstring (date-time) · timestamptz
placeholderstring · textyes
default_valuestring · textyes
visible_whenobject · jsonbyes
validationobject · jsonb

Primary key: id

Unique: form_id, target, target_key

Checks:

  • CHECK ((length(btrim(label)) > 0))
  • CHECK ((col_span = ANY (ARRAY[1, 2])))
  • CHECK (((jsonb_typeof(validation) = 'object'::text) AND ((visible_when IS NULL) OR (jsonb_typeof(visible_when) = 'object'::text))))

PublicForm

Table public_form · Resource public_forms

  • GET /api/v1/public_forms
  • GET /api/v1/public_forms/{id}
ColumnTypeNullableAllowed valuesReferences
idstring (uuid) · uuid
organization_idstring (uuid) · uuidorganization
workspace_idstring (uuid) · uuidworkspace
kindstring · textlead, table
slugstring · text
titlestring · text
descriptionstring · textyes
success_messagestring · textyes
is_activeboolean · bool
created_bystring (uuid) · uuidyesapp_user
created_atstring (date-time) · timestamptz
updated_atstring (date-time) · timestamptz
data_table_idstring (uuid) · uuidyesdata_table
saved_view_idstring (uuid) · uuidyes
consent_textstring · textyes
redirect_urlstring · textyes
settingsobject · jsonb

Primary key: id

Checks:

  • CHECK (((redirect_url IS NULL) OR (redirect_url ~ '^https?://'::text)))
  • CHECK ((jsonb_typeof(settings) = 'object'::text))
  • CHECK (((slug ~ '^[a-z0-9]+(-[a-z0-9]+)*$'::text) AND ((length(slug) >= 1) AND (length(slug) <= 80))))
  • CHECK (((kind = 'table'::text) = (data_table_id IS NOT NULL)))

Events

Workflows and webhooks react to these events.

EventWhat happensPayload
forms.fields_changedA form’s field list was replaced.id, count
forms.form_closedA public form stopped answering.id
forms.form_savedA public form was created or edited.id, slug, isActive
forms.submission_discardedA failed submission was set aside.id, formId
forms.submission_failedA retried submission was refused again.id, formId
forms.submission_recoveredA failed submission was retried and went in.id, formId
forms.submissions_exportedSubmissions were taken out of the platform.formId, count
forms.submittedA stranger filled a form in and a record was created.formId, slug, recordId
Was this page helpful?