Base URL: https://api.cornerspot.net
Authentication: requests use a bearer token, for example Authorization: Bearer YOUR_API_KEY.
List a form's fields
GET /c/api/v1/forms/{form_id}/fields
Returns every field on the form, ordered by position. Requires forms_read + the Forms module.
Path parameters
| FIELD | TYPE | DESCRIPTION |
|---|---|---|
form_id | string · required | Form definition id. |
Code samples
curl --request GET \
--url 'https://api.cornerspot.net/c/api/v1/forms/abc123/fields' \
--header 'Authorization: Bearer YOUR_API_KEY'
const response = await fetch('https://api.cornerspot.net/c/api/v1/forms/abc123/fields', {
method: 'GET',
headers: {
'Authorization': 'Bearer YOUR_API_KEY'
}
});
const data = await response.json();
console.log(data);
// Node.js 18+ (native fetch)
const response = await fetch('https://api.cornerspot.net/c/api/v1/forms/abc123/fields', {
method: 'GET',
headers: {
'Authorization': 'Bearer YOUR_API_KEY'
}
});
if (!response.ok) {
throw new Error(`Request failed: ${response.status}`);
}
const data = await response.json();
console.log(data);
import json
import urllib.request
url = "https://api.cornerspot.net/c/api/v1/forms/abc123/fields"
headers = {
"Authorization": "Bearer YOUR_API_KEY",
}
request = urllib.request.Request(url, headers=headers, method="GET")
with urllib.request.urlopen(request) as response:
data = json.loads(response.read().decode("utf-8"))
print(data)
require "json"
require "net/http"
uri = URI("https://api.cornerspot.net/c/api/v1/forms/abc123/fields")
request = Net::HTTP::Get.new(uri)
request["Authorization"] = "Bearer YOUR_API_KEY"
response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: uri.scheme == "https") do |http|
http.request(request)
end
puts JSON.parse(response.body)
<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => 'https://api.cornerspot.net/c/api/v1/forms/abc123/fields',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CUSTOMREQUEST => 'GET',
CURLOPT_HTTPHEADER => [
'Authorization: Bearer YOUR_API_KEY',
],
]);
$response = curl_exec($curl);
curl_close($curl);
$data = json_decode($response, true);
print_r($data);
Responses
| STATUS | DESCRIPTION |
|---|---|
200 OK | the form's fields in display order |
401 Unauthorized | missing or invalid API key |
404 Not Found | no such form in this team |
Add a field to a form
POST /c/api/v1/forms/{form_id}/fields
Adds one field to the form — the API equivalent of dragging a field into the dashboard builder. Two field sources:
standard — maps to a first-class Contact attribute via field_mapping (first_name, last_name, email, phone, ...). Submissions write straight onto the contact. custom — your own typed field (field_key + data_type). Enum types (single_enum, multi_enum) take an options array. Set map_to_contact: true to also reflect the value onto the contact as a custom field.
Layout: width is a percentage of the form row — fields wrap onto shared rows, so two width: 50 fields render side by side, three width: 33 make a three-column row, and width: 100 takes a full row. New fields append to the end of the form unless position is given; use POST /forms/{id}/reorder_fields to re-sequence the whole form.
Requires forms_write.
Path parameters
| FIELD | TYPE | DESCRIPTION |
|---|---|---|
form_id | string · required | Form definition id. |
Request body
| FIELD | TYPE | DESCRIPTION |
|---|---|---|
field | object · required | Writable form-field attributes. field_source decides the shape: standard fields map to a Contact attribute via field_mapping (no field_key/data_type); custom fields need a unique snake_case field_key and a data_type (and may reflect onto the Contact via map_to_contact). |
field.field_source | string one of standard, custom | Required on create. |
field.field_mapping | string | Standard fields only. Contact attribute key, e.g. first_name, last_name, email, phone. |
field.field_key | string | Custom fields only. Unique per form. |
field.data_type | string one of text, textarea, number, currency, date, datetime, … | Custom fields only. |
field.label | string | Required. |
field.placeholder | string | |
field.help_text | string | |
field.required | boolean | |
field.width | integer one of 25, 33, 50, 66, 75, 100 | Column width as a percentage of the form row — fields wrap onto shared rows (e.g. two 50s sit side by side). |
field.position | integer | 0-based position. Defaults to the end of the form on create. Use POST /forms/{id}/reorder_fields for whole-form reordering. |
field.default_value | object | Default value; an array for multi_enum fields. |
field.map_to_contact | boolean | Custom fields only: also write this value onto the submitting contact as a custom field. |
field.validation_rules | object | Per-type rules: min_length/max_length/pattern (text), min/max (number), max_file_size_bytes/allowed_content_types (attachment). |
field.options[] | array of object | Enum fields (single_enum/multi_enum): the selectable options. On update, include id to keep/edit an option, _destroy: true to remove it; options without id are created. |
field.options[].id | string | Present when editing an existing option. |
field.options[].label | string | |
field.options[].value | string | |
field.options[].position | integer | |
field.options[]._destroy | boolean | Update only: remove this option. |
Code samples
curl --request POST \
--url 'https://api.cornerspot.net/c/api/v1/forms/abc123/fields' \
--header 'Content-Type: application/json' \
--header 'Authorization: Bearer YOUR_API_KEY' \
--data '{
"field": {
"field_source": "standard",
"field_mapping": "email",
"label": "Work email",
"placeholder": "you@company.com",
"required": true,
"width": 50
}
}'
const response = await fetch('https://api.cornerspot.net/c/api/v1/forms/abc123/fields', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer YOUR_API_KEY'
},
body: JSON.stringify({
"field": {
"field_source": "standard",
"field_mapping": "email",
"label": "Work email",
"placeholder": "you@company.com",
"required": true,
"width": 50
}
})
});
const data = await response.json();
console.log(data);
// Node.js 18+ (native fetch)
const response = await fetch('https://api.cornerspot.net/c/api/v1/forms/abc123/fields', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer YOUR_API_KEY'
},
body: JSON.stringify({
"field": {
"field_source": "standard",
"field_mapping": "email",
"label": "Work email",
"placeholder": "you@company.com",
"required": true,
"width": 50
}
})
});
if (!response.ok) {
throw new Error(`Request failed: ${response.status}`);
}
const data = await response.json();
console.log(data);
import json
import urllib.request
url = "https://api.cornerspot.net/c/api/v1/forms/abc123/fields"
payload = json.dumps({
"field": {
"field_source": "standard",
"field_mapping": "email",
"label": "Work email",
"placeholder": "you@company.com",
"required": true,
"width": 50
}
}).encode("utf-8")
headers = {
"Content-Type": "application/json",
"Authorization": "Bearer YOUR_API_KEY",
}
request = urllib.request.Request(url, data=payload, headers=headers, method="POST")
with urllib.request.urlopen(request) as response:
data = json.loads(response.read().decode("utf-8"))
print(data)
require "json"
require "net/http"
uri = URI("https://api.cornerspot.net/c/api/v1/forms/abc123/fields")
request = Net::HTTP::Post.new(uri)
request["Content-Type"] = "application/json"
request["Authorization"] = "Bearer YOUR_API_KEY"
request.body = JSON.generate({
"field": {
"field_source": "standard",
"field_mapping": "email",
"label": "Work email",
"placeholder": "you@company.com",
"required": true,
"width": 50
}
})
response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: uri.scheme == "https") do |http|
http.request(request)
end
puts JSON.parse(response.body)
<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => 'https://api.cornerspot.net/c/api/v1/forms/abc123/fields',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_HTTPHEADER => [
'Content-Type: application/json',
'Authorization: Bearer YOUR_API_KEY',
],
CURLOPT_POSTFIELDS => json_encode([
'field' => [
'field_source' => 'standard',
'field_mapping' => 'email',
'label' => 'Work email',
'placeholder' => 'you@company.com',
'required' => true,
'width' => 50,
],
]),
]);
$response = curl_exec($curl);
curl_close($curl);
$data = json_decode($response, true);
print_r($data);
Responses
| STATUS | DESCRIPTION |
|---|---|
201 Created | created (appended to the end of the form unless position given) |
403 Forbidden | the key lacks the forms_write scope |
404 Not Found | no such form in this team |
422 Unprocessable Content | validation errors (e.g. custom field without a field_key) |
Get a field
GET /c/api/v1/forms/{form_id}/fields/{id}
Returns a single field with its options. Requires forms_read.
Path parameters
| FIELD | TYPE | DESCRIPTION |
|---|---|---|
form_id | string · required | Form definition id. |
id | string · required | Field id. |
Code samples
curl --request GET \
--url 'https://api.cornerspot.net/c/api/v1/forms/abc123/fields/abc123' \
--header 'Authorization: Bearer YOUR_API_KEY'
const response = await fetch('https://api.cornerspot.net/c/api/v1/forms/abc123/fields/abc123', {
method: 'GET',
headers: {
'Authorization': 'Bearer YOUR_API_KEY'
}
});
const data = await response.json();
console.log(data);
// Node.js 18+ (native fetch)
const response = await fetch('https://api.cornerspot.net/c/api/v1/forms/abc123/fields/abc123', {
method: 'GET',
headers: {
'Authorization': 'Bearer YOUR_API_KEY'
}
});
if (!response.ok) {
throw new Error(`Request failed: ${response.status}`);
}
const data = await response.json();
console.log(data);
import json
import urllib.request
url = "https://api.cornerspot.net/c/api/v1/forms/abc123/fields/abc123"
headers = {
"Authorization": "Bearer YOUR_API_KEY",
}
request = urllib.request.Request(url, headers=headers, method="GET")
with urllib.request.urlopen(request) as response:
data = json.loads(response.read().decode("utf-8"))
print(data)
require "json"
require "net/http"
uri = URI("https://api.cornerspot.net/c/api/v1/forms/abc123/fields/abc123")
request = Net::HTTP::Get.new(uri)
request["Authorization"] = "Bearer YOUR_API_KEY"
response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: uri.scheme == "https") do |http|
http.request(request)
end
puts JSON.parse(response.body)
<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => 'https://api.cornerspot.net/c/api/v1/forms/abc123/fields/abc123',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CUSTOMREQUEST => 'GET',
CURLOPT_HTTPHEADER => [
'Authorization: Bearer YOUR_API_KEY',
],
]);
$response = curl_exec($curl);
curl_close($curl);
$data = json_decode($response, true);
print_r($data);
Responses
| STATUS | DESCRIPTION |
|---|---|
200 OK | the field |
404 Not Found | no such field on this form |
Update a field
PATCH /c/api/v1/forms/{form_id}/fields/{id}
Updates any field attribute — relabel, resize (width), reposition, toggle required, edit validation rules, or manage enum options (include id to keep/edit an option, _destroy: true to remove one; options without an id are created). Flipping map_to_contact links/unlinks the reflected Contact custom field. Requires forms_write.
Path parameters
| FIELD | TYPE | DESCRIPTION |
|---|---|---|
form_id | string · required | Form definition id. |
id | string · required | Field id. |
Request body
| FIELD | TYPE | DESCRIPTION |
|---|---|---|
field | object · required | Writable form-field attributes. field_source decides the shape: standard fields map to a Contact attribute via field_mapping (no field_key/data_type); custom fields need a unique snake_case field_key and a data_type (and may reflect onto the Contact via map_to_contact). |
field.field_source | string one of standard, custom | Required on create. |
field.field_mapping | string | Standard fields only. Contact attribute key, e.g. first_name, last_name, email, phone. |
field.field_key | string | Custom fields only. Unique per form. |
field.data_type | string one of text, textarea, number, currency, date, datetime, … | Custom fields only. |
field.label | string | Required. |
field.placeholder | string | |
field.help_text | string | |
field.required | boolean | |
field.width | integer one of 25, 33, 50, 66, 75, 100 | Column width as a percentage of the form row — fields wrap onto shared rows (e.g. two 50s sit side by side). |
field.position | integer | 0-based position. Defaults to the end of the form on create. Use POST /forms/{id}/reorder_fields for whole-form reordering. |
field.default_value | object | Default value; an array for multi_enum fields. |
field.map_to_contact | boolean | Custom fields only: also write this value onto the submitting contact as a custom field. |
field.validation_rules | object | Per-type rules: min_length/max_length/pattern (text), min/max (number), max_file_size_bytes/allowed_content_types (attachment). |
field.options[] | array of object | Enum fields (single_enum/multi_enum): the selectable options. On update, include id to keep/edit an option, _destroy: true to remove it; options without id are created. |
field.options[].id | string | Present when editing an existing option. |
field.options[].label | string | |
field.options[].value | string | |
field.options[].position | integer | |
field.options[]._destroy | boolean | Update only: remove this option. |
Code samples
curl --request PATCH \
--url 'https://api.cornerspot.net/c/api/v1/forms/abc123/fields/abc123' \
--header 'Content-Type: application/json' \
--header 'Authorization: Bearer YOUR_API_KEY' \
--data '{
"field": {
"label": "Preferred style",
"width": 50
}
}'
const response = await fetch('https://api.cornerspot.net/c/api/v1/forms/abc123/fields/abc123', {
method: 'PATCH',
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer YOUR_API_KEY'
},
body: JSON.stringify({
"field": {
"label": "Preferred style",
"width": 50
}
})
});
const data = await response.json();
console.log(data);
// Node.js 18+ (native fetch)
const response = await fetch('https://api.cornerspot.net/c/api/v1/forms/abc123/fields/abc123', {
method: 'PATCH',
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer YOUR_API_KEY'
},
body: JSON.stringify({
"field": {
"label": "Preferred style",
"width": 50
}
})
});
if (!response.ok) {
throw new Error(`Request failed: ${response.status}`);
}
const data = await response.json();
console.log(data);
import json
import urllib.request
url = "https://api.cornerspot.net/c/api/v1/forms/abc123/fields/abc123"
payload = json.dumps({
"field": {
"label": "Preferred style",
"width": 50
}
}).encode("utf-8")
headers = {
"Content-Type": "application/json",
"Authorization": "Bearer YOUR_API_KEY",
}
request = urllib.request.Request(url, data=payload, headers=headers, method="PATCH")
with urllib.request.urlopen(request) as response:
data = json.loads(response.read().decode("utf-8"))
print(data)
require "json"
require "net/http"
uri = URI("https://api.cornerspot.net/c/api/v1/forms/abc123/fields/abc123")
request = Net::HTTP::Patch.new(uri)
request["Content-Type"] = "application/json"
request["Authorization"] = "Bearer YOUR_API_KEY"
request.body = JSON.generate({
"field": {
"label": "Preferred style",
"width": 50
}
})
response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: uri.scheme == "https") do |http|
http.request(request)
end
puts JSON.parse(response.body)
<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => 'https://api.cornerspot.net/c/api/v1/forms/abc123/fields/abc123',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CUSTOMREQUEST => 'PATCH',
CURLOPT_HTTPHEADER => [
'Content-Type: application/json',
'Authorization: Bearer YOUR_API_KEY',
],
CURLOPT_POSTFIELDS => json_encode([
'field' => [
'label' => 'Preferred style',
'width' => 50,
],
]),
]);
$response = curl_exec($curl);
curl_close($curl);
$data = json_decode($response, true);
print_r($data);
Responses
| STATUS | DESCRIPTION |
|---|---|
200 OK | updated |
404 Not Found | no such field on this form |
422 Unprocessable Content | validation errors (e.g. width not in 25/33/50/66/75/100) |
Remove a field
DELETE /c/api/v1/forms/{form_id}/fields/{id}
Deletes the field (and its options) from the form. Requires forms_write.
Path parameters
| FIELD | TYPE | DESCRIPTION |
|---|---|---|
form_id | string · required | Form definition id. |
id | string · required | Field id. |
Code samples
curl --request DELETE \
--url 'https://api.cornerspot.net/c/api/v1/forms/abc123/fields/abc123' \
--header 'Authorization: Bearer YOUR_API_KEY'
const response = await fetch('https://api.cornerspot.net/c/api/v1/forms/abc123/fields/abc123', {
method: 'DELETE',
headers: {
'Authorization': 'Bearer YOUR_API_KEY'
}
});
const data = await response.json();
console.log(data);
// Node.js 18+ (native fetch)
const response = await fetch('https://api.cornerspot.net/c/api/v1/forms/abc123/fields/abc123', {
method: 'DELETE',
headers: {
'Authorization': 'Bearer YOUR_API_KEY'
}
});
if (!response.ok) {
throw new Error(`Request failed: ${response.status}`);
}
const data = await response.json();
console.log(data);
import json
import urllib.request
url = "https://api.cornerspot.net/c/api/v1/forms/abc123/fields/abc123"
headers = {
"Authorization": "Bearer YOUR_API_KEY",
}
request = urllib.request.Request(url, headers=headers, method="DELETE")
with urllib.request.urlopen(request) as response:
data = json.loads(response.read().decode("utf-8"))
print(data)
require "json"
require "net/http"
uri = URI("https://api.cornerspot.net/c/api/v1/forms/abc123/fields/abc123")
request = Net::HTTP::Delete.new(uri)
request["Authorization"] = "Bearer YOUR_API_KEY"
response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: uri.scheme == "https") do |http|
http.request(request)
end
puts JSON.parse(response.body)
<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => 'https://api.cornerspot.net/c/api/v1/forms/abc123/fields/abc123',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CUSTOMREQUEST => 'DELETE',
CURLOPT_HTTPHEADER => [
'Authorization: Bearer YOUR_API_KEY',
],
]);
$response = curl_exec($curl);
curl_close($curl);
$data = json_decode($response, true);
print_r($data);
Responses
| STATUS | DESCRIPTION |
|---|---|
204 No Content | deleted |
404 Not Found | no such field on this form |
List forms
GET /c/api/v1/forms
Returns a paginated list of form definitions for the team. Supports full-text search over name and slug. Requires forms_read + the Forms module.
Query parameters
| FIELD | TYPE | DESCRIPTION |
|---|---|---|
query | string | Full-text search over form name and slug. |
filter[search] | string | Alias for query; either key triggers the search. |
page | integer | 1-based page number. |
per_page | integer | Page size (1–100, default 25). |
Code samples
curl --request GET \
--url 'https://api.cornerspot.net/c/api/v1/forms' \
--header 'Authorization: Bearer YOUR_API_KEY'
const response = await fetch('https://api.cornerspot.net/c/api/v1/forms', {
method: 'GET',
headers: {
'Authorization': 'Bearer YOUR_API_KEY'
}
});
const data = await response.json();
console.log(data);
// Node.js 18+ (native fetch)
const response = await fetch('https://api.cornerspot.net/c/api/v1/forms', {
method: 'GET',
headers: {
'Authorization': 'Bearer YOUR_API_KEY'
}
});
if (!response.ok) {
throw new Error(`Request failed: ${response.status}`);
}
const data = await response.json();
console.log(data);
import json
import urllib.request
url = "https://api.cornerspot.net/c/api/v1/forms"
headers = {
"Authorization": "Bearer YOUR_API_KEY",
}
request = urllib.request.Request(url, headers=headers, method="GET")
with urllib.request.urlopen(request) as response:
data = json.loads(response.read().decode("utf-8"))
print(data)
require "json"
require "net/http"
uri = URI("https://api.cornerspot.net/c/api/v1/forms")
request = Net::HTTP::Get.new(uri)
request["Authorization"] = "Bearer YOUR_API_KEY"
response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: uri.scheme == "https") do |http|
http.request(request)
end
puts JSON.parse(response.body)
<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => 'https://api.cornerspot.net/c/api/v1/forms',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CUSTOMREQUEST => 'GET',
CURLOPT_HTTPHEADER => [
'Authorization: Bearer YOUR_API_KEY',
],
]);
$response = curl_exec($curl);
curl_close($curl);
$data = json_decode($response, true);
print_r($data);
Responses
| STATUS | DESCRIPTION |
|---|---|
200 OK | a page of forms |
401 Unauthorized | missing or invalid API key |
402 Payment Required | the team's plan doesn't include the Forms module |
403 Forbidden | the key lacks the forms_read scope |
Create a form
POST /c/api/v1/forms
Creates a form definition — optionally a complete one in a single call: metadata, submission behavior, the full fields array (same shape as POST /forms/{id}/fields, positions defaulting to the given order), and per-channel style_config staged ahead of publishing.
A slug is generated from the name when not provided. When fields is omitted, the team's default field set is seeded onto the blank form (explicit params always win); when fields is given, only your fields are created.
style_config.single_page / style_config.portal accept a FormStyleConfig each — including the note (note_content) and its positioning (note_position: top/bottom/left/right, note_size, note_container_background). Styles staged here take effect on the live page when you call POST /forms/{id}/publish. See the request examples for each note orientation.
Requires forms_write.
Request body
| FIELD | TYPE | DESCRIPTION |
|---|---|---|
form | object | |
form.name | string · required | Required. |
form.description | string | |
form.slug | string | URL-safe slug; auto-generated from name when blank. |
form.submit_button_text | string | |
form.success_message | string | |
form.redirect_url | string | |
form.form_layout | string one of stacked, horizontal, inline | Label placement: above (stacked), beside (horizontal), or floating (inline). |
form.create_contact_on_submission | boolean | Create/update a Contact from each submission (team default when omitted). |
form.allow_multiple_submissions_per_client | boolean | |
form.allow_multiple_submissions_per_email | boolean | |
form.require_email_verification | boolean | |
form.fields[] | array of object | Create the form's fields in one call. Positions default to array order. Omit to seed the team's default field set instead. |
form.fields[].field_source | string one of standard, custom | Required on create. |
form.fields[].field_mapping | string | Standard fields only. Contact attribute key, e.g. first_name, last_name, email, phone. |
form.fields[].field_key | string | Custom fields only. Unique per form. |
form.fields[].data_type | string one of text, textarea, number, currency, date, datetime, … | Custom fields only. |
form.fields[].label | string | Required. |
form.fields[].placeholder | string | |
form.fields[].help_text | string | |
form.fields[].required | boolean | |
form.fields[].width | integer one of 25, 33, 50, 66, 75, 100 | Column width as a percentage of the form row — fields wrap onto shared rows (e.g. two 50s sit side by side). |
form.fields[].position | integer | 0-based position. Defaults to the end of the form on create. Use POST /forms/{id}/reorder_fields for whole-form reordering. |
form.fields[].default_value | object | Default value; an array for multi_enum fields. |
form.fields[].map_to_contact | boolean | Custom fields only: also write this value onto the submitting contact as a custom field. |
form.fields[].validation_rules | object | Per-type rules: min_length/max_length/pattern (text), min/max (number), max_file_size_bytes/allowed_content_types (attachment). |
form.fields[].options[] | array of object | Enum fields (single_enum/multi_enum): the selectable options. On update, include id to keep/edit an option, _destroy: true to remove it; options without id are created. |
form.style_config | object | Stage per-channel styling before publishing. Keys are deep-merged per scope; positioning keys are single_page-only. |
form.style_config.single_page | object | Per-channel style configuration. Every key is optional and nullable; a null/absent key inherits the platform default. Keys are grouped by prefix: form container (background_color, border_radius, container_padding, container_shadow, container_max_width, form_border_), fields (field_, label_), submit button (button_, submit_button_text), page (page_background, page_margin — shareable link only), the form note (note_), and the post-submit success message (success_). Unknown keys are silently dropped; enum-valued keys with invalid values are dropped (the default wins). |
form.style_config.portal | object | Per-channel style configuration. Every key is optional and nullable; a null/absent key inherits the platform default. Keys are grouped by prefix: form container (background_color, border_radius, container_padding, container_shadow, container_max_width, form_border_), fields (field_, label_), submit button (button_, submit_button_text), page (page_background, page_margin — shareable link only), the form note (note_), and the post-submit success message (success_). Unknown keys are silently dropped; enum-valued keys with invalid values are dropped (the default wins). |
Code samples
curl --request POST \
--url 'https://api.cornerspot.net/c/api/v1/forms' \
--header 'Content-Type: application/json' \
--header 'Authorization: Bearer YOUR_API_KEY' \
--data '{
"form": {
"name": "Contact us"
}
}'
const response = await fetch('https://api.cornerspot.net/c/api/v1/forms', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer YOUR_API_KEY'
},
body: JSON.stringify({
"form": {
"name": "Contact us"
}
})
});
const data = await response.json();
console.log(data);
// Node.js 18+ (native fetch)
const response = await fetch('https://api.cornerspot.net/c/api/v1/forms', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer YOUR_API_KEY'
},
body: JSON.stringify({
"form": {
"name": "Contact us"
}
})
});
if (!response.ok) {
throw new Error(`Request failed: ${response.status}`);
}
const data = await response.json();
console.log(data);
import json
import urllib.request
url = "https://api.cornerspot.net/c/api/v1/forms"
payload = json.dumps({
"form": {
"name": "Contact us"
}
}).encode("utf-8")
headers = {
"Content-Type": "application/json",
"Authorization": "Bearer YOUR_API_KEY",
}
request = urllib.request.Request(url, data=payload, headers=headers, method="POST")
with urllib.request.urlopen(request) as response:
data = json.loads(response.read().decode("utf-8"))
print(data)
require "json"
require "net/http"
uri = URI("https://api.cornerspot.net/c/api/v1/forms")
request = Net::HTTP::Post.new(uri)
request["Content-Type"] = "application/json"
request["Authorization"] = "Bearer YOUR_API_KEY"
request.body = JSON.generate({
"form": {
"name": "Contact us"
}
})
response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: uri.scheme == "https") do |http|
http.request(request)
end
puts JSON.parse(response.body)
<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => 'https://api.cornerspot.net/c/api/v1/forms',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_HTTPHEADER => [
'Content-Type: application/json',
'Authorization: Bearer YOUR_API_KEY',
],
CURLOPT_POSTFIELDS => json_encode([
'form' => [
'name' => 'Contact us',
],
]),
]);
$response = curl_exec($curl);
curl_close($curl);
$data = json_decode($response, true);
print_r($data);
Responses
| STATUS | DESCRIPTION |
|---|---|
201 Created | created |
401 Unauthorized | missing or invalid API key |
402 Payment Required | the team's plan doesn't include the Forms module |
403 Forbidden | the key lacks the forms_write scope |
422 Unprocessable Content | validation errors (e.g. name blank) |
Get a form
GET /c/api/v1/forms/{id}
Returns a single form definition with full publication state and embed config. Requires forms_read.
Path parameters
| FIELD | TYPE | DESCRIPTION |
|---|---|---|
id | string · required | Form definition id. |
Code samples
curl --request GET \
--url 'https://api.cornerspot.net/c/api/v1/forms/abc123' \
--header 'Authorization: Bearer YOUR_API_KEY'
const response = await fetch('https://api.cornerspot.net/c/api/v1/forms/abc123', {
method: 'GET',
headers: {
'Authorization': 'Bearer YOUR_API_KEY'
}
});
const data = await response.json();
console.log(data);
// Node.js 18+ (native fetch)
const response = await fetch('https://api.cornerspot.net/c/api/v1/forms/abc123', {
method: 'GET',
headers: {
'Authorization': 'Bearer YOUR_API_KEY'
}
});
if (!response.ok) {
throw new Error(`Request failed: ${response.status}`);
}
const data = await response.json();
console.log(data);
import json
import urllib.request
url = "https://api.cornerspot.net/c/api/v1/forms/abc123"
headers = {
"Authorization": "Bearer YOUR_API_KEY",
}
request = urllib.request.Request(url, headers=headers, method="GET")
with urllib.request.urlopen(request) as response:
data = json.loads(response.read().decode("utf-8"))
print(data)
require "json"
require "net/http"
uri = URI("https://api.cornerspot.net/c/api/v1/forms/abc123")
request = Net::HTTP::Get.new(uri)
request["Authorization"] = "Bearer YOUR_API_KEY"
response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: uri.scheme == "https") do |http|
http.request(request)
end
puts JSON.parse(response.body)
<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => 'https://api.cornerspot.net/c/api/v1/forms/abc123',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CUSTOMREQUEST => 'GET',
CURLOPT_HTTPHEADER => [
'Authorization: Bearer YOUR_API_KEY',
],
]);
$response = curl_exec($curl);
curl_close($curl);
$data = json_decode($response, true);
print_r($data);
Responses
| STATUS | DESCRIPTION |
|---|---|
200 OK | the form |
401 Unauthorized | missing or invalid API key |
404 Not Found | no such form in this team |
Update a form
PATCH /c/api/v1/forms/{id}
Updates form metadata, submission behavior, and/or staged per-channel style_config (deep-merged per scope; send a key as null to reset it — see FormStyleConfig for the note positioning keys). Field management is done via the /forms/{id}/fields endpoints and reorder_fields. Requires forms_write.
Path parameters
| FIELD | TYPE | DESCRIPTION |
|---|---|---|
id | string · required | Form definition id. |
Request body
| FIELD | TYPE | DESCRIPTION |
|---|---|---|
form | object | |
form.name | string | |
form.description | string | |
form.slug | string | |
form.submit_button_text | string | |
form.success_message | string | |
form.redirect_url | string | |
form.form_layout | string one of stacked, horizontal, inline | |
form.create_contact_on_submission | boolean | |
form.allow_multiple_submissions_per_client | boolean | |
form.allow_multiple_submissions_per_email | boolean | |
form.require_email_verification | boolean | |
form.style_config | object | Stage per-channel styling; keys are deep-merged per scope. |
form.style_config.single_page | object | Per-channel style configuration. Every key is optional and nullable; a null/absent key inherits the platform default. Keys are grouped by prefix: form container (background_color, border_radius, container_padding, container_shadow, container_max_width, form_border_), fields (field_, label_), submit button (button_, submit_button_text), page (page_background, page_margin — shareable link only), the form note (note_), and the post-submit success message (success_). Unknown keys are silently dropped; enum-valued keys with invalid values are dropped (the default wins). |
form.style_config.portal | object | Per-channel style configuration. Every key is optional and nullable; a null/absent key inherits the platform default. Keys are grouped by prefix: form container (background_color, border_radius, container_padding, container_shadow, container_max_width, form_border_), fields (field_, label_), submit button (button_, submit_button_text), page (page_background, page_margin — shareable link only), the form note (note_), and the post-submit success message (success_). Unknown keys are silently dropped; enum-valued keys with invalid values are dropped (the default wins). |
Code samples
curl --request PATCH \
--url 'https://api.cornerspot.net/c/api/v1/forms/abc123' \
--header 'Content-Type: application/json' \
--header 'Authorization: Bearer YOUR_API_KEY' \
--data '{
"form": {
"name": "Updated form name"
}
}'
const response = await fetch('https://api.cornerspot.net/c/api/v1/forms/abc123', {
method: 'PATCH',
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer YOUR_API_KEY'
},
body: JSON.stringify({
"form": {
"name": "Updated form name"
}
})
});
const data = await response.json();
console.log(data);
// Node.js 18+ (native fetch)
const response = await fetch('https://api.cornerspot.net/c/api/v1/forms/abc123', {
method: 'PATCH',
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer YOUR_API_KEY'
},
body: JSON.stringify({
"form": {
"name": "Updated form name"
}
})
});
if (!response.ok) {
throw new Error(`Request failed: ${response.status}`);
}
const data = await response.json();
console.log(data);
import json
import urllib.request
url = "https://api.cornerspot.net/c/api/v1/forms/abc123"
payload = json.dumps({
"form": {
"name": "Updated form name"
}
}).encode("utf-8")
headers = {
"Content-Type": "application/json",
"Authorization": "Bearer YOUR_API_KEY",
}
request = urllib.request.Request(url, data=payload, headers=headers, method="PATCH")
with urllib.request.urlopen(request) as response:
data = json.loads(response.read().decode("utf-8"))
print(data)
require "json"
require "net/http"
uri = URI("https://api.cornerspot.net/c/api/v1/forms/abc123")
request = Net::HTTP::Patch.new(uri)
request["Content-Type"] = "application/json"
request["Authorization"] = "Bearer YOUR_API_KEY"
request.body = JSON.generate({
"form": {
"name": "Updated form name"
}
})
response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: uri.scheme == "https") do |http|
http.request(request)
end
puts JSON.parse(response.body)
<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => 'https://api.cornerspot.net/c/api/v1/forms/abc123',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CUSTOMREQUEST => 'PATCH',
CURLOPT_HTTPHEADER => [
'Content-Type: application/json',
'Authorization: Bearer YOUR_API_KEY',
],
CURLOPT_POSTFIELDS => json_encode([
'form' => [
'name' => 'Updated form name',
],
]),
]);
$response = curl_exec($curl);
curl_close($curl);
$data = json_decode($response, true);
print_r($data);
Responses
| STATUS | DESCRIPTION |
|---|---|
200 OK | updated |
404 Not Found | no such form in this team |
Delete (soft) a form
DELETE /c/api/v1/forms/{id}
Soft-deletes the form definition and records a DeletionEvent so the form lands in Trash (restorable and purgeable). Requires forms_write.
Path parameters
| FIELD | TYPE | DESCRIPTION |
|---|---|---|
id | string · required | Form definition id. |
Code samples
curl --request DELETE \
--url 'https://api.cornerspot.net/c/api/v1/forms/abc123' \
--header 'Authorization: Bearer YOUR_API_KEY'
const response = await fetch('https://api.cornerspot.net/c/api/v1/forms/abc123', {
method: 'DELETE',
headers: {
'Authorization': 'Bearer YOUR_API_KEY'
}
});
const data = await response.json();
console.log(data);
// Node.js 18+ (native fetch)
const response = await fetch('https://api.cornerspot.net/c/api/v1/forms/abc123', {
method: 'DELETE',
headers: {
'Authorization': 'Bearer YOUR_API_KEY'
}
});
if (!response.ok) {
throw new Error(`Request failed: ${response.status}`);
}
const data = await response.json();
console.log(data);
import json
import urllib.request
url = "https://api.cornerspot.net/c/api/v1/forms/abc123"
headers = {
"Authorization": "Bearer YOUR_API_KEY",
}
request = urllib.request.Request(url, headers=headers, method="DELETE")
with urllib.request.urlopen(request) as response:
data = json.loads(response.read().decode("utf-8"))
print(data)
require "json"
require "net/http"
uri = URI("https://api.cornerspot.net/c/api/v1/forms/abc123")
request = Net::HTTP::Delete.new(uri)
request["Authorization"] = "Bearer YOUR_API_KEY"
response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: uri.scheme == "https") do |http|
http.request(request)
end
puts JSON.parse(response.body)
<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => 'https://api.cornerspot.net/c/api/v1/forms/abc123',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CUSTOMREQUEST => 'DELETE',
CURLOPT_HTTPHEADER => [
'Authorization: Bearer YOUR_API_KEY',
],
]);
$response = curl_exec($curl);
curl_close($curl);
$data = json_decode($response, true);
print_r($data);
Responses
| STATUS | DESCRIPTION |
|---|---|
204 No Content | deleted |
404 Not Found | no such form in this team |
Duplicate a form
POST /c/api/v1/forms/{id}/duplicate
Deep-copies a form (all fields and their options) into a new unpublished form. The copy's name gets a (Copy) suffix; the slug is auto-derived. The copy's single-page publication state is reset to unpublished. Requires forms_write.
Path parameters
| FIELD | TYPE | DESCRIPTION |
|---|---|---|
id | string · required | Form definition id to duplicate. |
Code samples
curl --request POST \
--url 'https://api.cornerspot.net/c/api/v1/forms/abc123/duplicate' \
--header 'Authorization: Bearer YOUR_API_KEY'
const response = await fetch('https://api.cornerspot.net/c/api/v1/forms/abc123/duplicate', {
method: 'POST',
headers: {
'Authorization': 'Bearer YOUR_API_KEY'
}
});
const data = await response.json();
console.log(data);
// Node.js 18+ (native fetch)
const response = await fetch('https://api.cornerspot.net/c/api/v1/forms/abc123/duplicate', {
method: 'POST',
headers: {
'Authorization': 'Bearer YOUR_API_KEY'
}
});
if (!response.ok) {
throw new Error(`Request failed: ${response.status}`);
}
const data = await response.json();
console.log(data);
import json
import urllib.request
url = "https://api.cornerspot.net/c/api/v1/forms/abc123/duplicate"
headers = {
"Authorization": "Bearer YOUR_API_KEY",
}
request = urllib.request.Request(url, headers=headers, method="POST")
with urllib.request.urlopen(request) as response:
data = json.loads(response.read().decode("utf-8"))
print(data)
require "json"
require "net/http"
uri = URI("https://api.cornerspot.net/c/api/v1/forms/abc123/duplicate")
request = Net::HTTP::Post.new(uri)
request["Authorization"] = "Bearer YOUR_API_KEY"
response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: uri.scheme == "https") do |http|
http.request(request)
end
puts JSON.parse(response.body)
<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => 'https://api.cornerspot.net/c/api/v1/forms/abc123/duplicate',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_HTTPHEADER => [
'Authorization: Bearer YOUR_API_KEY',
],
]);
$response = curl_exec($curl);
curl_close($curl);
$data = json_decode($response, true);
print_r($data);
Responses
| STATUS | DESCRIPTION |
|---|---|
201 Created | created (the new duplicate form) |
401 Unauthorized | missing or invalid API key |
403 Forbidden | the key lacks the forms_write scope |
404 Not Found | no such form in this team |
Reorder form fields
POST /c/api/v1/forms/{id}/reorder_fields
Repositions every field in the form. The request body must list every field id belonging to the form — partial lists are rejected with 422 invalid_field_ordering. Requires forms_write.
Path parameters
| FIELD | TYPE | DESCRIPTION |
|---|---|---|
id | string · required | Form definition id. |
Request body
| FIELD | TYPE | DESCRIPTION |
|---|---|---|
field_ids[] | array of string · required | Required — complete ordered list of every field id on this form. |
Code samples
curl --request POST \
--url 'https://api.cornerspot.net/c/api/v1/forms/abc123/reorder_fields' \
--header 'Content-Type: application/json' \
--header 'Authorization: Bearer YOUR_API_KEY' \
--data '{
"field_ids": [
"1f2e3d4c-5b6a-4789-9c0d-1e2f3a4b5c6d"
]
}'
const response = await fetch('https://api.cornerspot.net/c/api/v1/forms/abc123/reorder_fields', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer YOUR_API_KEY'
},
body: JSON.stringify({
"field_ids": [
"1f2e3d4c-5b6a-4789-9c0d-1e2f3a4b5c6d"
]
})
});
const data = await response.json();
console.log(data);
// Node.js 18+ (native fetch)
const response = await fetch('https://api.cornerspot.net/c/api/v1/forms/abc123/reorder_fields', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer YOUR_API_KEY'
},
body: JSON.stringify({
"field_ids": [
"1f2e3d4c-5b6a-4789-9c0d-1e2f3a4b5c6d"
]
})
});
if (!response.ok) {
throw new Error(`Request failed: ${response.status}`);
}
const data = await response.json();
console.log(data);
import json
import urllib.request
url = "https://api.cornerspot.net/c/api/v1/forms/abc123/reorder_fields"
payload = json.dumps({
"field_ids": [
"1f2e3d4c-5b6a-4789-9c0d-1e2f3a4b5c6d"
]
}).encode("utf-8")
headers = {
"Content-Type": "application/json",
"Authorization": "Bearer YOUR_API_KEY",
}
request = urllib.request.Request(url, data=payload, headers=headers, method="POST")
with urllib.request.urlopen(request) as response:
data = json.loads(response.read().decode("utf-8"))
print(data)
require "json"
require "net/http"
uri = URI("https://api.cornerspot.net/c/api/v1/forms/abc123/reorder_fields")
request = Net::HTTP::Post.new(uri)
request["Content-Type"] = "application/json"
request["Authorization"] = "Bearer YOUR_API_KEY"
request.body = JSON.generate({
"field_ids": [
"1f2e3d4c-5b6a-4789-9c0d-1e2f3a4b5c6d"
]
})
response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: uri.scheme == "https") do |http|
http.request(request)
end
puts JSON.parse(response.body)
<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => 'https://api.cornerspot.net/c/api/v1/forms/abc123/reorder_fields',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_HTTPHEADER => [
'Content-Type: application/json',
'Authorization: Bearer YOUR_API_KEY',
],
CURLOPT_POSTFIELDS => json_encode([
'field_ids' => [
'1f2e3d4c-5b6a-4789-9c0d-1e2f3a4b5c6d',
],
]),
]);
$response = curl_exec($curl);
curl_close($curl);
$data = json_decode($response, true);
print_r($data);
Responses
| STATUS | DESCRIPTION |
|---|---|
200 OK | fields reordered; returns the updated form |
401 Unauthorized | missing or invalid API key |
403 Forbidden | the key lacks the forms_write scope |
404 Not Found | no such form in this team |
422 Unprocessable Content | field_ids is not a complete ordering |
Publish a form
POST /c/api/v1/forms/{id}/publish
Drives all publish pathways in a single request. Provide single_page, portal, or both. Set enabled: false on a pathway to unpublish it.
Styling — each pathway takes a partial style object (see FormStyleConfig); provided keys are deep-merged into that channel's stored style, so you can publish repeatedly and only send what changed. Send a key with null to reset it to the platform default.
Note positioning (shareable link) — the form note (style.note_content) renders top of the form by default. Set style.note_position to bottom, or to left/right for a full-height split page: the note column takes note_size (1/4/1/2/3/4) of the width, note_background paints the whole column, note_container_background/note_radius/note_shadow style the note's inner container, and note_max_width is ignored. See the request examples for each orientation. Left/right apply only to the shareable link; the portal channel strips positioning keys.
Custom note (shareable link) — set style.note_mode to custom to author the note as raw HTML instead of the rich-text note_content. style.note_custom_html holds the markup (permissive no-JS sanitize: presentational/structural HTML, <style>, inline styles, class/id/data-/aria-, images, links, and https iframes survive; <script>, on* handlers, and javascript: URLs are stripped) and style.note_custom_css holds CSS stored verbatim. Both are capped at 64 KB and apply to the shareable link only (the portal strips them). Positioning still applies; the custom note fills the note slot in every position. rich (default) keeps note_content.
Custom code (shareable link) — single_page.custom_code stores CSS/JS that is injected verbatim into the published page (see FormCustomCode). Provided keys are updated, omitted keys keep their stored values; send an empty string to clear a field. js_placement is head or body (default). Code renders only while custom_code.enabled is true; disabling keeps the stored code. Each field is capped at 128 KB (oversized requests return 422).
Returns 200 when all CDN artifact pushes succeed, 207 when some artifact hosts failed (the form is still published; see warnings.single_page_failed_hosts). At least one pathway must be present in the request body, otherwise 422 no_publish_pathway is returned. Requires forms_write.
Path parameters
| FIELD | TYPE | DESCRIPTION |
|---|---|---|
id | string · required | Form definition id. |
Request body
| FIELD | TYPE | DESCRIPTION |
|---|---|---|
single_page | object | Shareable-link channel. Omit to leave it unchanged. |
single_page.enabled | boolean | false to unpublish. |
single_page.slug | string | Override the public URL slug. |
single_page.powered_by_enabled | boolean | Turning the branding off requires the white-label addon; without it the prior value is kept. |
single_page.style | object | Per-channel style configuration. Every key is optional and nullable; a null/absent key inherits the platform default. Keys are grouped by prefix: form container (background_color, border_radius, container_padding, container_shadow, container_max_width, form_border_), fields (field_, label_), submit button (button_, submit_button_text), page (page_background, page_margin — shareable link only), the form note (note_), and the post-submit success message (success_). Unknown keys are silently dropped; enum-valued keys with invalid values are dropped (the default wins). |
single_page.style.note_content | string | Rich-text HTML for the form note (sanitized server-side). The note only renders when this is present. |
single_page.style.note_mode | string one of rich, custom | Shareable-link (single_page) note authoring mode. rich (default) renders note_content. custom renders note_custom_html + note_custom_css instead, giving full control of the note markup and styling. Invalid values are dropped (mode falls back to rich); the portal strips this key. |
single_page.style.note_custom_html | string | Custom-mode note HTML (used when note_mode is custom). Sanitized server-side with a permissive no-JS allowlist: presentational and structural HTML, <style> blocks, inline styles, class/id/data-/aria-, images, links, and https iframes are kept; <script>, on* handlers, and javascript: URLs are removed. Capped at 64 KB. single_page only; the portal strips it. |
single_page.style.note_custom_css | string | Custom-mode note CSS (used when note_mode is custom). Stored verbatim and loaded with the note on the shareable-link page. Capped at 64 KB. single_page only; the portal strips it. |
single_page.style.note_position | string one of top, bottom, left, right | Where the note renders relative to the form on the shareable-link page. top (default) and bottom render the note as a card above/below the form. left/right split the page into two full-height columns — the note column and the form column. Left/right apply ONLY to the shareable-link (single_page) channel; the portal strips these keys and embeds always render top. |
single_page.style.note_size | string one of 1/4, 1/2, 3/4 | Left/right positions only: the note column's fraction of the page width (default 1/2). The form column takes the remainder. |
single_page.style.note_background | string | Top/bottom: the note card's background. Left/right: the background of the ENTIRE full-height note column. |
single_page.style.note_container_background | string | Left/right positions only: the background of the note's inner container (the card inside the colored column). Default transparent, so the column reads as one panel until set. |
single_page.style.note_radius | string | Note card corner radius (e.g. 8px). In left/right positions this rounds the inner container ("Container corner radius"), never the column. |
single_page.style.note_shadow | string | Note card box-shadow. In left/right positions this shadows the inner container ("Container shadow"), never the column. |
single_page.style.note_max_width | string | Note card max width (e.g. 480px). Top/bottom positions only — ignored in left/right (the card fills its column). |
single_page.style.note_text_color | string | |
single_page.style.success_message | string | Per-channel override of the post-submit confirmation (sanitized HTML). |
single_page.style.redirect_url | string | Per-channel override: redirect after submit instead of showing the success message. |
single_page.style.submit_button_text | string | |
single_page.style.background_color | string | Form container background. |
single_page.style.page_background | string | Shareable-link page background (behind the form; in left/right note positions it shows behind the form column). |
single_page.custom_code | object | Custom code injected verbatim into the published shareable-link page (guides/form-custom-code). When enabled, css is emitted as <style id="cs-form-custom-css"> at the end of <head> (after the form theme styles, so your rules win the cascade) and js as <script id="cs-form-custom-js"> at the end of <head> or </body> per js_placement. Nothing is emitted while enabled is false or a field is empty; stored code is preserved when disabled. Each field is capped at 128 KB. |
single_page.custom_code.enabled | boolean | Master switch. False stops emission but keeps the stored code. |
single_page.custom_code.css | string | Verbatim CSS. Empty string clears it. |
single_page.custom_code.js | string | Verbatim JavaScript. Empty string clears it. Runs only on the published page (never in the dashboard preview). |
single_page.custom_code.js_placement | string one of head, body | Where the script tag is injected. body (default) places it after the form widget script. |
portal | object | Member-portal channel. Omit to leave it unchanged. |
portal.enabled | boolean | false to unpublish from the portal. |
portal.audience_criteria | object | Filter criteria for portal recipients: invitation, active, q, status[], account_id[]. |
portal.allow_resubmission | boolean | When true, members can re-open and overwrite their submission. Only applied when the key is present. |
portal.style | object | Per-channel style configuration. Every key is optional and nullable; a null/absent key inherits the platform default. Keys are grouped by prefix: form container (background_color, border_radius, container_padding, container_shadow, container_max_width, form_border_), fields (field_, label_), submit button (button_, submit_button_text), page (page_background, page_margin — shareable link only), the form note (note_), and the post-submit success message (success_). Unknown keys are silently dropped; enum-valued keys with invalid values are dropped (the default wins). |
portal.style.note_content | string | Rich-text HTML for the form note (sanitized server-side). The note only renders when this is present. |
portal.style.note_mode | string one of rich, custom | Shareable-link (single_page) note authoring mode. rich (default) renders note_content. custom renders note_custom_html + note_custom_css instead, giving full control of the note markup and styling. Invalid values are dropped (mode falls back to rich); the portal strips this key. |
portal.style.note_custom_html | string | Custom-mode note HTML (used when note_mode is custom). Sanitized server-side with a permissive no-JS allowlist: presentational and structural HTML, <style> blocks, inline styles, class/id/data-/aria-, images, links, and https iframes are kept; <script>, on* handlers, and javascript: URLs are removed. Capped at 64 KB. single_page only; the portal strips it. |
portal.style.note_custom_css | string | Custom-mode note CSS (used when note_mode is custom). Stored verbatim and loaded with the note on the shareable-link page. Capped at 64 KB. single_page only; the portal strips it. |
portal.style.note_position | string one of top, bottom, left, right | Where the note renders relative to the form on the shareable-link page. top (default) and bottom render the note as a card above/below the form. left/right split the page into two full-height columns — the note column and the form column. Left/right apply ONLY to the shareable-link (single_page) channel; the portal strips these keys and embeds always render top. |
portal.style.note_size | string one of 1/4, 1/2, 3/4 | Left/right positions only: the note column's fraction of the page width (default 1/2). The form column takes the remainder. |
portal.style.note_background | string | Top/bottom: the note card's background. Left/right: the background of the ENTIRE full-height note column. |
portal.style.note_container_background | string | Left/right positions only: the background of the note's inner container (the card inside the colored column). Default transparent, so the column reads as one panel until set. |
portal.style.note_radius | string | Note card corner radius (e.g. 8px). In left/right positions this rounds the inner container ("Container corner radius"), never the column. |
portal.style.note_shadow | string | Note card box-shadow. In left/right positions this shadows the inner container ("Container shadow"), never the column. |
portal.style.note_max_width | string | Note card max width (e.g. 480px). Top/bottom positions only — ignored in left/right (the card fills its column). |
portal.style.note_text_color | string | |
portal.style.success_message | string | Per-channel override of the post-submit confirmation (sanitized HTML). |
portal.style.redirect_url | string | Per-channel override: redirect after submit instead of showing the success message. |
portal.style.submit_button_text | string | |
portal.style.background_color | string | Form container background. |
portal.style.page_background | string | Shareable-link page background (behind the form; in left/right note positions it shows behind the form column). |
Code samples
curl --request POST \
--url 'https://api.cornerspot.net/c/api/v1/forms/abc123/publish' \
--header 'Content-Type: application/json' \
--header 'Authorization: Bearer YOUR_API_KEY' \
--data '{
"single_page": {
"enabled": true,
"slug": "trial-class",
"style": {
"note_content": "<p><strong>Welcome!</strong> Tell us about your dancer and we'\''ll match a class.</p>",
"note_background": "#f0f4ff",
"note_radius": "10px",
"note_shadow": "0 4px 12px rgba(0,0,0,0.1)",
"note_max_width": "480px"
}
}
}'
const response = await fetch('https://api.cornerspot.net/c/api/v1/forms/abc123/publish', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer YOUR_API_KEY'
},
body: JSON.stringify({
"single_page": {
"enabled": true,
"slug": "trial-class",
"style": {
"note_content": "<p><strong>Welcome!</strong> Tell us about your dancer and we'll match a class.</p>",
"note_background": "#f0f4ff",
"note_radius": "10px",
"note_shadow": "0 4px 12px rgba(0,0,0,0.1)",
"note_max_width": "480px"
}
}
})
});
const data = await response.json();
console.log(data);
// Node.js 18+ (native fetch)
const response = await fetch('https://api.cornerspot.net/c/api/v1/forms/abc123/publish', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer YOUR_API_KEY'
},
body: JSON.stringify({
"single_page": {
"enabled": true,
"slug": "trial-class",
"style": {
"note_content": "<p><strong>Welcome!</strong> Tell us about your dancer and we'll match a class.</p>",
"note_background": "#f0f4ff",
"note_radius": "10px",
"note_shadow": "0 4px 12px rgba(0,0,0,0.1)",
"note_max_width": "480px"
}
}
})
});
if (!response.ok) {
throw new Error(`Request failed: ${response.status}`);
}
const data = await response.json();
console.log(data);
import json
import urllib.request
url = "https://api.cornerspot.net/c/api/v1/forms/abc123/publish"
payload = json.dumps({
"single_page": {
"enabled": true,
"slug": "trial-class",
"style": {
"note_content": "<p><strong>Welcome!</strong> Tell us about your dancer and we'll match a class.</p>",
"note_background": "#f0f4ff",
"note_radius": "10px",
"note_shadow": "0 4px 12px rgba(0,0,0,0.1)",
"note_max_width": "480px"
}
}
}).encode("utf-8")
headers = {
"Content-Type": "application/json",
"Authorization": "Bearer YOUR_API_KEY",
}
request = urllib.request.Request(url, data=payload, headers=headers, method="POST")
with urllib.request.urlopen(request) as response:
data = json.loads(response.read().decode("utf-8"))
print(data)
require "json"
require "net/http"
uri = URI("https://api.cornerspot.net/c/api/v1/forms/abc123/publish")
request = Net::HTTP::Post.new(uri)
request["Content-Type"] = "application/json"
request["Authorization"] = "Bearer YOUR_API_KEY"
request.body = JSON.generate({
"single_page": {
"enabled": true,
"slug": "trial-class",
"style": {
"note_content": "<p><strong>Welcome!</strong> Tell us about your dancer and we'll match a class.</p>",
"note_background": "#f0f4ff",
"note_radius": "10px",
"note_shadow": "0 4px 12px rgba(0,0,0,0.1)",
"note_max_width": "480px"
}
}
})
response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: uri.scheme == "https") do |http|
http.request(request)
end
puts JSON.parse(response.body)
<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => 'https://api.cornerspot.net/c/api/v1/forms/abc123/publish',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_HTTPHEADER => [
'Content-Type: application/json',
'Authorization: Bearer YOUR_API_KEY',
],
CURLOPT_POSTFIELDS => json_encode([
'single_page' => [
'enabled' => true,
'slug' => 'trial-class',
'style' => [
'note_content' => '<p><strong>Welcome!</strong> Tell us about your dancer and we\'ll match a class.</p>',
'note_background' => '#f0f4ff',
'note_radius' => '10px',
'note_shadow' => '0 4px 12px rgba(0,0,0,0.1)',
'note_max_width' => '480px',
],
],
]),
]);
$response = curl_exec($curl);
curl_close($curl);
$data = json_decode($response, true);
print_r($data);
Responses
| STATUS | DESCRIPTION |
|---|---|
200 OK | custom-note HTML mode round-trips (guides/form-note-custom-html) |
401 Unauthorized | missing or invalid API key |
403 Forbidden | the key lacks the forms_write scope |
404 Not Found | no such form in this team |
422 Unprocessable Content | no publish pathway provided (no_publish_pathway) |
