Forms

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

FIELDTYPEDESCRIPTION
form_idstring · requiredForm 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

STATUSDESCRIPTION
200 OKthe form's fields in display order
401 Unauthorizedmissing or invalid API key
404 Not Foundno 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

FIELDTYPEDESCRIPTION
form_idstring · requiredForm definition id.

Request body

FIELDTYPEDESCRIPTION
fieldobject · requiredWritable 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_sourcestring
one of standard, custom
Required on create.
field.field_mappingstringStandard fields only. Contact attribute key, e.g. first_name, last_name, email, phone.
field.field_keystringCustom fields only. Unique per form.
field.data_typestring
one of text, textarea, number, currency, date, datetime, …
Custom fields only.
field.labelstringRequired.
field.placeholderstring
field.help_textstring
field.requiredboolean
field.widthinteger
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.positioninteger0-based position. Defaults to the end of the form on create. Use POST /forms/{id}/reorder_fields for whole-form reordering.
field.default_valueobjectDefault value; an array for multi_enum fields.
field.map_to_contactbooleanCustom fields only: also write this value onto the submitting contact as a custom field.
field.validation_rulesobjectPer-type rules: min_length/max_length/pattern (text), min/max (number), max_file_size_bytes/allowed_content_types (attachment).
field.options[]array of objectEnum 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[].idstringPresent when editing an existing option.
field.options[].labelstring
field.options[].valuestring
field.options[].positioninteger
field.options[]._destroybooleanUpdate 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

STATUSDESCRIPTION
201 Createdcreated (appended to the end of the form unless position given)
403 Forbiddenthe key lacks the forms_write scope
404 Not Foundno such form in this team
422 Unprocessable Contentvalidation 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

FIELDTYPEDESCRIPTION
form_idstring · requiredForm definition id.
idstring · requiredField 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

STATUSDESCRIPTION
200 OKthe field
404 Not Foundno 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

FIELDTYPEDESCRIPTION
form_idstring · requiredForm definition id.
idstring · requiredField id.

Request body

FIELDTYPEDESCRIPTION
fieldobject · requiredWritable 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_sourcestring
one of standard, custom
Required on create.
field.field_mappingstringStandard fields only. Contact attribute key, e.g. first_name, last_name, email, phone.
field.field_keystringCustom fields only. Unique per form.
field.data_typestring
one of text, textarea, number, currency, date, datetime, …
Custom fields only.
field.labelstringRequired.
field.placeholderstring
field.help_textstring
field.requiredboolean
field.widthinteger
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.positioninteger0-based position. Defaults to the end of the form on create. Use POST /forms/{id}/reorder_fields for whole-form reordering.
field.default_valueobjectDefault value; an array for multi_enum fields.
field.map_to_contactbooleanCustom fields only: also write this value onto the submitting contact as a custom field.
field.validation_rulesobjectPer-type rules: min_length/max_length/pattern (text), min/max (number), max_file_size_bytes/allowed_content_types (attachment).
field.options[]array of objectEnum 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[].idstringPresent when editing an existing option.
field.options[].labelstring
field.options[].valuestring
field.options[].positioninteger
field.options[]._destroybooleanUpdate 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

STATUSDESCRIPTION
200 OKupdated
404 Not Foundno such field on this form
422 Unprocessable Contentvalidation 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

FIELDTYPEDESCRIPTION
form_idstring · requiredForm definition id.
idstring · requiredField 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

STATUSDESCRIPTION
204 No Contentdeleted
404 Not Foundno 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

FIELDTYPEDESCRIPTION
querystringFull-text search over form name and slug.
filter[search]stringAlias for query; either key triggers the search.
pageinteger1-based page number.
per_pageintegerPage 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

STATUSDESCRIPTION
200 OKa page of forms
401 Unauthorizedmissing or invalid API key
402 Payment Requiredthe team's plan doesn't include the Forms module
403 Forbiddenthe 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

FIELDTYPEDESCRIPTION
formobject
form.namestring · requiredRequired.
form.descriptionstring
form.slugstringURL-safe slug; auto-generated from name when blank.
form.submit_button_textstring
form.success_messagestring
form.redirect_urlstring
form.form_layoutstring
one of stacked, horizontal, inline
Label placement: above (stacked), beside (horizontal), or floating (inline).
form.create_contact_on_submissionbooleanCreate/update a Contact from each submission (team default when omitted).
form.allow_multiple_submissions_per_clientboolean
form.allow_multiple_submissions_per_emailboolean
form.require_email_verificationboolean
form.fields[]array of objectCreate 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_sourcestring
one of standard, custom
Required on create.
form.fields[].field_mappingstringStandard fields only. Contact attribute key, e.g. first_name, last_name, email, phone.
form.fields[].field_keystringCustom fields only. Unique per form.
form.fields[].data_typestring
one of text, textarea, number, currency, date, datetime, …
Custom fields only.
form.fields[].labelstringRequired.
form.fields[].placeholderstring
form.fields[].help_textstring
form.fields[].requiredboolean
form.fields[].widthinteger
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[].positioninteger0-based position. Defaults to the end of the form on create. Use POST /forms/{id}/reorder_fields for whole-form reordering.
form.fields[].default_valueobjectDefault value; an array for multi_enum fields.
form.fields[].map_to_contactbooleanCustom fields only: also write this value onto the submitting contact as a custom field.
form.fields[].validation_rulesobjectPer-type rules: min_length/max_length/pattern (text), min/max (number), max_file_size_bytes/allowed_content_types (attachment).
form.fields[].options[]array of objectEnum 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_configobjectStage per-channel styling before publishing. Keys are deep-merged per scope; positioning keys are single_page-only.
form.style_config.single_pageobjectPer-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.portalobjectPer-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

STATUSDESCRIPTION
201 Createdcreated
401 Unauthorizedmissing or invalid API key
402 Payment Requiredthe team's plan doesn't include the Forms module
403 Forbiddenthe key lacks the forms_write scope
422 Unprocessable Contentvalidation 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

FIELDTYPEDESCRIPTION
idstring · requiredForm 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

STATUSDESCRIPTION
200 OKthe form
401 Unauthorizedmissing or invalid API key
404 Not Foundno 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

FIELDTYPEDESCRIPTION
idstring · requiredForm definition id.

Request body

FIELDTYPEDESCRIPTION
formobject
form.namestring
form.descriptionstring
form.slugstring
form.submit_button_textstring
form.success_messagestring
form.redirect_urlstring
form.form_layoutstring
one of stacked, horizontal, inline
form.create_contact_on_submissionboolean
form.allow_multiple_submissions_per_clientboolean
form.allow_multiple_submissions_per_emailboolean
form.require_email_verificationboolean
form.style_configobjectStage per-channel styling; keys are deep-merged per scope.
form.style_config.single_pageobjectPer-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.portalobjectPer-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

STATUSDESCRIPTION
200 OKupdated
404 Not Foundno 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

FIELDTYPEDESCRIPTION
idstring · requiredForm 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

STATUSDESCRIPTION
204 No Contentdeleted
404 Not Foundno 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

FIELDTYPEDESCRIPTION
idstring · requiredForm 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

STATUSDESCRIPTION
201 Createdcreated (the new duplicate form)
401 Unauthorizedmissing or invalid API key
403 Forbiddenthe key lacks the forms_write scope
404 Not Foundno 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

FIELDTYPEDESCRIPTION
idstring · requiredForm definition id.

Request body

FIELDTYPEDESCRIPTION
field_ids[]array of string · requiredRequired — 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

STATUSDESCRIPTION
200 OKfields reordered; returns the updated form
401 Unauthorizedmissing or invalid API key
403 Forbiddenthe key lacks the forms_write scope
404 Not Foundno such form in this team
422 Unprocessable Contentfield_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

FIELDTYPEDESCRIPTION
idstring · requiredForm definition id.

Request body

FIELDTYPEDESCRIPTION
single_pageobjectShareable-link channel. Omit to leave it unchanged.
single_page.enabledbooleanfalse to unpublish.
single_page.slugstringOverride the public URL slug.
single_page.powered_by_enabledbooleanTurning the branding off requires the white-label addon; without it the prior value is kept.
single_page.styleobjectPer-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_contentstringRich-text HTML for the form note (sanitized server-side). The note only renders when this is present.
single_page.style.note_modestring
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_htmlstringCustom-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_cssstringCustom-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_positionstring
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_sizestring
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_backgroundstringTop/bottom: the note card's background. Left/right: the background of the ENTIRE full-height note column.
single_page.style.note_container_backgroundstringLeft/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_radiusstringNote 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_shadowstringNote card box-shadow. In left/right positions this shadows the inner container ("Container shadow"), never the column.
single_page.style.note_max_widthstringNote card max width (e.g. 480px). Top/bottom positions only — ignored in left/right (the card fills its column).
single_page.style.note_text_colorstring
single_page.style.success_messagestringPer-channel override of the post-submit confirmation (sanitized HTML).
single_page.style.redirect_urlstringPer-channel override: redirect after submit instead of showing the success message.
single_page.style.submit_button_textstring
single_page.style.background_colorstringForm container background.
single_page.style.page_backgroundstringShareable-link page background (behind the form; in left/right note positions it shows behind the form column).
single_page.custom_codeobjectCustom 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.enabledbooleanMaster switch. False stops emission but keeps the stored code.
single_page.custom_code.cssstringVerbatim CSS. Empty string clears it.
single_page.custom_code.jsstringVerbatim JavaScript. Empty string clears it. Runs only on the published page (never in the dashboard preview).
single_page.custom_code.js_placementstring
one of head, body
Where the script tag is injected. body (default) places it after the form widget script.
portalobjectMember-portal channel. Omit to leave it unchanged.
portal.enabledbooleanfalse to unpublish from the portal.
portal.audience_criteriaobjectFilter criteria for portal recipients: invitation, active, q, status[], account_id[].
portal.allow_resubmissionbooleanWhen true, members can re-open and overwrite their submission. Only applied when the key is present.
portal.styleobjectPer-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_contentstringRich-text HTML for the form note (sanitized server-side). The note only renders when this is present.
portal.style.note_modestring
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_htmlstringCustom-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_cssstringCustom-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_positionstring
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_sizestring
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_backgroundstringTop/bottom: the note card's background. Left/right: the background of the ENTIRE full-height note column.
portal.style.note_container_backgroundstringLeft/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_radiusstringNote card corner radius (e.g. 8px). In left/right positions this rounds the inner container ("Container corner radius"), never the column.
portal.style.note_shadowstringNote card box-shadow. In left/right positions this shadows the inner container ("Container shadow"), never the column.
portal.style.note_max_widthstringNote card max width (e.g. 480px). Top/bottom positions only — ignored in left/right (the card fills its column).
portal.style.note_text_colorstring
portal.style.success_messagestringPer-channel override of the post-submit confirmation (sanitized HTML).
portal.style.redirect_urlstringPer-channel override: redirect after submit instead of showing the success message.
portal.style.submit_button_textstring
portal.style.background_colorstringForm container background.
portal.style.page_backgroundstringShareable-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

STATUSDESCRIPTION
200 OKcustom-note HTML mode round-trips (guides/form-note-custom-html)
401 Unauthorizedmissing or invalid API key
403 Forbiddenthe key lacks the forms_write scope
404 Not Foundno such form in this team
422 Unprocessable Contentno publish pathway provided (no_publish_pathway)

Was this article helpful?