curl --request POST \
--url https://formhug.ai/api/v1/f/{form_token}/entries \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"field_values": {
"field_1": "Jane Doe",
"field_2": "Welcome!"
}
}
'import requests
url = "https://formhug.ai/api/v1/f/{form_token}/entries"
payload = { "field_values": {
"field_1": "Jane Doe",
"field_2": "Welcome!"
} }
headers = {
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
body: JSON.stringify({field_values: {field_1: 'Jane Doe', field_2: 'Welcome!'}})
};
fetch('https://formhug.ai/api/v1/f/{form_token}/entries', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://formhug.ai/api/v1/f/{form_token}/entries",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'field_values' => [
'field_1' => 'Jane Doe',
'field_2' => 'Welcome!'
]
]),
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>",
"Content-Type: application/json"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://formhug.ai/api/v1/f/{form_token}/entries"
payload := strings.NewReader("{\n \"field_values\": {\n \"field_1\": \"Jane Doe\",\n \"field_2\": \"Welcome!\"\n }\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Authorization", "Bearer <token>")
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://formhug.ai/api/v1/f/{form_token}/entries")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"field_values\": {\n \"field_1\": \"Jane Doe\",\n \"field_2\": \"Welcome!\"\n }\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://formhug.ai/api/v1/f/{form_token}/entries")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Authorization"] = 'Bearer <token>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"field_values\": {\n \"field_1\": \"Jane Doe\",\n \"field_2\": \"Welcome!\"\n }\n}"
response = http.request(request)
puts response.read_body{
"data": {
"serial_number": 42,
"token": "aB2cD9eF",
"created_at": "2023-11-07T05:31:56Z",
"updated_at": "2023-11-07T05:31:56Z"
}
}{
"error": "Authentication failed"
}{
"error": "Name can't be blank",
"error_details": [
{
"attribute": "name",
"message": "can't be blank"
}
]
}{
"error": "Form not found"
}Submit an entry to a published form
Submit one entry to a published form as the authenticated user. Does not require ownership of the form, but the form must accept submissions (open, password validated, etc.). The form’s fill-frequency limits are enforced (per user, per IP, or per submitted field value; a per-device limit cannot identify API clients and does not restrict them). The request IP and user agent are recorded as the entry’s submission metadata.
Required scope: form:respond.
curl --request POST \
--url https://formhug.ai/api/v1/f/{form_token}/entries \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"field_values": {
"field_1": "Jane Doe",
"field_2": "Welcome!"
}
}
'import requests
url = "https://formhug.ai/api/v1/f/{form_token}/entries"
payload = { "field_values": {
"field_1": "Jane Doe",
"field_2": "Welcome!"
} }
headers = {
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
body: JSON.stringify({field_values: {field_1: 'Jane Doe', field_2: 'Welcome!'}})
};
fetch('https://formhug.ai/api/v1/f/{form_token}/entries', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://formhug.ai/api/v1/f/{form_token}/entries",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'field_values' => [
'field_1' => 'Jane Doe',
'field_2' => 'Welcome!'
]
]),
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>",
"Content-Type: application/json"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://formhug.ai/api/v1/f/{form_token}/entries"
payload := strings.NewReader("{\n \"field_values\": {\n \"field_1\": \"Jane Doe\",\n \"field_2\": \"Welcome!\"\n }\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Authorization", "Bearer <token>")
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://formhug.ai/api/v1/f/{form_token}/entries")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"field_values\": {\n \"field_1\": \"Jane Doe\",\n \"field_2\": \"Welcome!\"\n }\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://formhug.ai/api/v1/f/{form_token}/entries")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Authorization"] = 'Bearer <token>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"field_values\": {\n \"field_1\": \"Jane Doe\",\n \"field_2\": \"Welcome!\"\n }\n}"
response = http.request(request)
puts response.read_body{
"data": {
"serial_number": 42,
"token": "aB2cD9eF",
"created_at": "2023-11-07T05:31:56Z",
"updated_at": "2023-11-07T05:31:56Z"
}
}{
"error": "Authentication failed"
}{
"error": "Name can't be blank",
"error_details": [
{
"attribute": "name",
"message": "can't be blank"
}
]
}{
"error": "Form not found"
}Authorizations
Personal Access Token prefixed with fh_. Sent as Authorization: Bearer fh_xxx.
The scope required by each endpoint is listed in that endpoint's description.
Headers
Access password, when the form is password-protected
Path Parameters
Token of a published form
Body
Map keyed by field api_code; unknown keys are dropped silently. Fields with private: true cannot be submitted here — their keys are ignored and the server fills each private field's predefined_value, if any.
{
"field_1": "Jane Doe",
"field_2": "Welcome!"
}
Response
Submitted
A submitted entry. In addition to the fixed fields below, every field of the
parent form appears as a top-level key on the entry under its api_code. The
JSON value type for each such key is determined by the corresponding field's
type.
Field value shapes
Each field of the parent form appears as a top-level key on the entry, keyed by its
api_code. The JSON shape of the value depends on the field's type:
Field type (type) | Submitted value |
|---|---|
short_text | String. The submitted text. |
long_text | String. Multi-line text. |
radio | Object { value, other_text?, extended_text? }, or null when unset. value is the api_code of the selected choice. other_text carries the respondent's free-text input and is only present when the selected choice has is_other: true. extended_text carries the free-text input for a choice that has allow_extended_text: true. The two text keys are mutually exclusive per choice (an is_other choice cannot also enable allow_extended_text) and both are omitted when blank. On write, a bare string is accepted as shorthand for { value: <string> }; null clears the field; text keys that don't match the selected choice's configuration are ignored. |
checkbox | Array of { value, other_text?, extended_text? } objects with the same per-choice semantics as radio. Empty array when nothing is selected. At most one element may carry other_text (the element whose value corresponds to the is_other choice). On write, null clears the field; bare strings in the array are accepted as shorthand for { value: <string> }; duplicate values are silently de-duplicated (first occurrence wins). |
image_radio | Object { value }, or null when unset. value is the api_code of the selected choice. Image choices support neither other_text nor extended_text. On write, a bare string is accepted as shorthand for { value: <string> }; null clears the field. |
image_checkbox | Array of { value } objects — value is the api_code of a selected choice. Empty array when nothing is selected. On write, null clears the field; bare strings in the array are accepted as shorthand; duplicate values are silently de-duplicated (first occurrence wins). |
dropdown | Object { value, other_text? }, or null when unset. Same value and other_text semantics as radio. dropdown does not support extended_text. |
number | Number. Stored as a float; integer-looking values may serialize without a decimal point. |
email | String — an email address. |
phone | Object { country_id, country_code, number }. country_id is an ISO 3166-1 alpha-2 country code (e.g. "CN", "US") and is required; country_code is the dialing code without + (e.g. "86", "1") and is optional on write — when omitted it is derived from country_id; number is the local number without the dialing-code prefix. |
date | Naive wall-clock string formatted per the field's precision: "YYYY-MM" (month), "YYYY-MM-DD" (day), "YYYY-MM-DD HH:mm" (minute), "YYYY-MM-DD HH:mm:ss" (second). Values are interpreted in the form's timezone setting. When no timezone is supplied at creation time, the form uses your account's timezone, falling back to UTC if that is unset or invalid. |
name | String — the submitted name. |
url | String — a URL. |
address | Object { address_line1, address_line2, city, state, postal_code, country }. country is an ISO 3166-1 alpha-2 country code; state is an ISO 3166-2 subdivision code (e.g. "CA", "NY"), not a localized label. |
rating | Integer — the chosen rating level (1..rating_max). |
nps | Integer — the selected score (0..10). |
attachment | Array of objects { id: string, name: string, url: string, file_size: integer, content_type: string }. url is a signed download link valid for 24 hours — refetch the entry to get a fresh link rather than caching it; name is the original filename. |
audio | Read-only object { id, name, url, file_size, content_type, duration } describing the recording (url is a signed download link valid for 24 hours; duration is the recording length). Null when no recording. Submitting a value for an audio field is not supported. |
cascade | Object { level_1: <api_code>, level_2: <api_code>, ... } — one key per level (the field's levels setting, default 2). Each value is the api_code of the choice selected at that level. |
ranking | Object { <choice_api_code>: <rank> } — rank is a positive integer, contiguous starting at 1 for the highest-ranked choice. Unranked choices are omitted. |
matrix | Nested object { <statement_api_code>: { <dimension_api_code>: <cell_value> }, ... }. Each cell reads and writes its dimension type's own entry value shape — e.g. a dropdown cell is { value: <choice_api_code> } (bare-string shorthand accepted on write), a number cell is a number. |
likert | Object { <statement_api_code>: [<choice_api_code>, ...] }. The value is always an array — a single-choice answer is wrapped in a one-element array. |
time | Object { hour: integer, minute: integer }, plus second: integer when the field has include_second enabled (e.g. { "hour": 9, "minute": 5 }). |
location | Object { longitude: number, latitude: number, address: string }. |
signature | Read-only string — a signed download URL for the signature image, valid for 24 hours. Refetch the entry to obtain a fresh link rather than caching it. Submitting a value for a signature field is not supported. |
matrix_rating | Nested object { <statement_api_code>: { <dimension_api_code>: <integer> } }. The inner integer is the chosen rating level (1..rating_max). |
table | Array of row objects [{ <dimension_api_code>: <cell_value>, ... }]. Each cell reads and writes its dimension type's own entry value shape — e.g. a dropdown cell is { value: <choice_api_code> } (bare-string shorthand accepted on write), a number cell is a number. |
product | Array [{ api_code, number, specification? }]. specification is present when the goods item has dimensions; it is an object of { <dim_api_code>: <option_api_code> }. |
booking | Array of reservations. On write: [{ api_code, scheduled_at, end_at?, number? }] — api_code is the reservation item; scheduled_at (and end_at) are ISO8601 date-time strings; number defaults to 1 (values other than 1 require allow_multiple_number: true). For a weekly-schedule item send scheduled_at on the target day. For a per-day-of-week time-slot item send scheduled_at = day + slot start and end_at = day + slot end, matching one of the item's configured slots (quota_setting.daily_time_range_quotas[].start_time / end_time). item_name / time_range_code are server-derived and ignored on write. Send [] to clear. On read: [{ api_code, scheduled_at, end_at, number, item_name, time_range_code }]. |
linked_form | On write: string — the entry_token of an entry in the associated form, or null to clear. Public submitters can discover valid entry_tokens via GET /api/v1/f/{form_token}/associated_entry_options. The server resolves the token against associated_form_token; an unknown token yields 422. On read: one of three shapes — (1) null if not linked; (2) { "deleted": true } if the linked entry has been deleted; (3) Object { entry_token, serial_number, associated_fields, display_fields } describing the linked entry, where associated_fields / display_fields map each api_code (from associated_field_api_codes / display_field_api_codes respectively) to that field's value, formatted per the linked field's own type (recursive — e.g. a date field renders as its naive wall-clock string, an attachment field renders as its array of file objects). |
formula | The computed result. A number, a naive date string (YYYY-MM-DD), or a string, depending on what the formula evaluates to. |
page_break | None — display-only field; never appears as an entry value. |
description | None — display-only field; never appears as an entry value. |
Show child attributes
Show child attributes
Was this page helpful?