> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://docs.pinnacle.sh/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs.pinnacle.sh/_mcp/server.

# Send Form

POST https://api.pinnacle.sh/forms/send
Content-Type: application/json

Send a form to a recipient over SMS or RCS, or mint a standalone submission URL.

Pass `form` as either an existing form id (`form_*`) or an inline `{ fields, ... }` definition to mint a new form for this send.

The delivery channel is inferred from `from`:

* `from: "agent_*"` → RCS (with optional SMS `fallback`)
* `from: "+E.164"` → SMS

When `to` is provided, Pinnacle dispatches a message whose body contains the submission URL and the recipient is recorded on the response: `submission.to` echoes the same E.164 number and `message_id` is the id of the outbound SMS/RCS.

When `to` is omitted, no message is sent — `submission.to` and `message_id` are both `null` — which is useful for embedding the URL in your own outreach.

On completion, a `FORM.SUBMISSION` webhook event is delivered to webhooks subscribed to the sender. See [Receiving Messages and User Events](/guides/messages/receiving).

Reference: https://docs.pinnacle.sh/api-reference/forms/send-form

## Authentication

- `PINNACLE-API-KEY` header (required) — API Key authentication via header

## Request

### Body (application/json)

This endpoint expects a SendFormRequest.

- `SendFormRequest`

## Response

### 200

Form sent (or URL minted when `to` is omitted).

- `forms_send_Response_200`

## Errors

### 400 Bad Request Error

Validation failed. The payload has missing required fields and/or invalid types.&#x20; See [https://zod.dev/error-formatting](https://zod.dev/error-formatting) for more information.

- `description` (string, required) — Human-readable summary of validation failures.
- `errors` (ZodErrorErrors, required) — Structured dictionary of issues containing two main sections: - `errors`: Array of global validation errors not tied to specific fields - `properties`: Object mapping field names to their specific validation errors, where each field contains an `errors` array

### 401 Unauthorized Error

The request lacks valid authentication credentials or the provided credentials are invalid.&#x20; Ensure you're including a valid API key in the request headers and that your account has the necessary permissions to access this endpoint.

- `error` (string, required) — Human-readable description of the error that occurred, corresponding to the HTTP status code.

### 404 Not Found Error

The requested resource could not be found.&#x20; This may occur if the identifier is incorrect, the resource has been deleted, or you don't have permission to access it.

- `error` (string, required) — Human-readable description of the error that occurred, corresponding to the HTTP status code.

### 500 Internal Server Error

An unexpected error occurred on Pinnacle's servers while processing your request.&#x20; If this error persists, please contact support with the request details.

- `error` (string, required) — Human-readable description of the error that occurred, corresponding to the HTTP status code.

## Types

### SendFormViaRcsRequest

Send a form from an RCS agent. `from` must be an `agent_*` id.

- `from` (string, required) — RCS agent id (`agent_*`).
- `form` (SendFormViaRcsRequestForm, required) — Existing form id or an inline definition that mints a new form for this send.
- `to` (string, optional) — Recipient phone in E.164. When present Pinnacle delivers the form URL via RCS (or the configured fallback) and FORM.SUBMISSION fires with `submission.to` populated. When omitted the call returns a submission URL without dispatching any message and the webhook later fires with `submission.to: null`.
- `fallback` (SendFormViaRcsRequestFallback, optional) — When RCS is unavailable on the recipient's device, fall back to SMS from this number. Requires `to`.
- `options` (SendFormOptions, optional) — Optional delivery knobs shared by RCS and SMS sends.

### SendFormViaSmsRequest

Send a form from a phone number. `from` must be an E.164 phone.

- `from` (string, required) — Sender phone in E.164.
- `form` (SendFormViaSmsRequestForm, required)
- `to` (string, optional) — Recipient phone in E.164. Omit to mint a URL-only form without sending a message.
- `options` (SendFormOptions, optional) — Optional delivery knobs shared by RCS and SMS sends.

### SendFormResponse

Successful `POST /forms/send` response. - When `to` was provided in the request, the message was dispatched: `submission.to` echoes that recipient and `message_id` is the id of the outbound SMS/RCS that carried the URL. - When `to` was omitted, no message was sent: both `submission.to` and `message_id` are `null` and only the submission URL is returned.

- `form` (Form, required) — A hosted form definition.
- `submission` (FormSubmission, required) — A single submission against a form.
- `message_id` (string, required, nullable) — Id of the outbound SMS/RCS message that delivered the URL. Null when the send was URL-only (no `to`).

### ScheduledFormSendResponse

Response shape for a scheduled form send — returned in place of `SendFormResponse` when `options.schedule` is set.

- `scheduleId` (string, required)
- `config` (ScheduleSchema, required) — Define when and how your message should be sent.
- `form` (ScheduledFormSendResponseForm, required)
- `submission` (ScheduledFormSendResponseSubmission, required)

### ZodErrorErrors

Structured dictionary of issues containing two main sections: - `errors`: Array of global validation errors not tied to specific fields - `properties`: Object mapping field names to their specific validation errors, where each field contains an `errors` array

### SendFormViaRcsRequestForm

Existing form id or an inline definition that mints a new form for this send.

### SendFormViaRcsRequestFallback

When RCS is unavailable on the recipient's device, fall back to SMS from this number. Requires `to`.

- `from` (string, required) — SMS phone in E.164 to deliver from when the recipient can't receive RCS.

### SendFormOptions

Optional delivery knobs shared by RCS and SMS sends.

- `schedule` (ScheduleSchema, optional) — Define when and how your message should be sent.
- `webview_mode` (enum, optional, default: FULL) — RCS webview size for the open-url button action. Ignored for SMS.
  - Allowed values: `FULL`, `HALF`, `TALL`

### SendFormViaSmsRequestForm

### Form

A hosted form definition.

- `id` (string, required) — Form id (starts with `form_`).
- `url` (string, required) — Public shareable URL of the form (`https://forms.pinnacle.sh/{form_id}`).
- `name` (string, required, nullable) — Human-readable name for the form. Rendered as the form's title.
- `description` (string, required, nullable) — Longer description rendered below the title.
- `fields` (list of FormField, required) — Ordered list of fields the recipient fills out.
- `can_update` (boolean, required) — When true, the recipient can reopen the submission URL and edit their answers. When false, the URL becomes read-only after the first submit.
- `expires_at` (string, required, nullable) — After this timestamp the form stops accepting submissions. Null means no expiration.
- `theme_override` (FormThemeOverride, required, nullable) — Per-form theme tweaks layered on top of your team's default theme. Null means the team defaults are used as-is.
- `submission_count` (integer, required) — Count of distinct completed submissions for this form. Does not increment on edits to an existing `can_update=true` submission.
- `last_submitted_at` (string, required, nullable) — Timestamp of the most recent submission event. Updated on each new submit and on each edit of a `can_update=true` submission.
- `archived_at` (string, required, nullable) — When set, the form is archived (soft-deleted) and cannot accept new submissions or be updated. Restore by PATCHing `archived_at` back to null.
- `created_at` (string, required) — ISO 8601 timestamp of when the form was created.
- `updated_at` (string, required) — ISO 8601 timestamp of the most recent form mutation.

### FormSubmission

A single submission against a form.

- `id` (string, required) — Submission id (starts with `fsub_`).
- `url` (string, required) — Public submission URL (`https://forms.pinnacle.sh/{submission_id}`) — the unguessable credential used by the recipient to fill out the form.
- `form_id` (string, required) — Id of the form this submission belongs to (starts with `form_`).
- `from` (string, required, nullable) — Sender identifier — E.164 phone number or RCS agent id. Null if the sender could no longer be resolved (e.g. the phone number or agent was deleted after the submission).
- `to` (string, required, nullable) — Recipient phone number (E.164). Null for URL-only sends (`to` was omitted at send time).
- `data` (map from string to any, required, nullable) — Submitted answers keyed by field `key`. Null if the submission has not been completed yet. Each value's shape depends on the field type: - `text`, `email`, `phone`, `url`, `textarea`, `select`, `radio` → string - `number` → number - `checkbox` (single) → boolean - `checkbox` (multi), `multiselect` → array of strings - Skipped optional field → null
- `ip_address` (string, required, nullable) — IP address the submission was POSTed from. Null for pending submissions.
- `user_agent` (string, required, nullable) — User-Agent header of the browser that submitted the form. Null for pending submissions.
- `submitted_at` (string, required, nullable) — Timestamp of completion. Null for pending submissions; updated on each edit of a `can_update=true` submission.
- `created_at` (string, required) — Timestamp of when the submission URL was minted.

### ScheduleSchema

Define when and how your message should be sent.

- `sendAt` (string, required) — The date and time you want your message to be sent. - Use [ISO 8601 format](https://en.wikipedia.org/wiki/ISO_8601), for example: `2024-12-17T14:30:00Z`. Format: `YYYY-MM-DDThh:mm` (e.g., `2024-12-25T14:30`). - This time must be at least 5 minutes in the future. - The message will be scheduled based on the `timezone` you provide. - If you set a `recurrence` schedule, this is the start date and time for the recurring schedule.
- `recurrence` (string, optional) — AWS cron expression for recurring schedules (6 fields).&#x20; [Learn more about cron expressions](https://docs.aws.amazon.com/eventbridge/latest/userguide/eb-scheduled-rule-pattern.html).&#x20; **Examples:** * `0 10 * * ? *` - Every day at 10:00 AM * `0/30 * * * ? *` - Every 30 minutes * `0 9 ? * MON-FRI *` - Every weekday at 9:00 AM * `0 12 1 * ? *` - First day of every month at noon
- `timezone` (string, optional, default: UTC) — IANA timezone identifier (e.g., `America/New_York`, `UTC`).&#x20; Defaults to `UTC` if not specified.
- `endDate` (string, optional) — Date and time when recurring messages should stop.&#x20; Format: `YYYY-MM-DDThh:mm`. Required if `recurrence` is set.

### ScheduledFormSendResponseForm

- `id` (string, required)
- `name` (string, required, nullable)

### ScheduledFormSendResponseSubmission

- `id` (string, required)
- `url` (string, required)

### SendFormViaRcsRequestForm1

The minimum shape needed to create a form. Used by `POST /forms` and inline by `POST /forms/send`.

- `fields` (list of FormField, required) — Ordered list of fields the recipient will fill out. At least one field is required.
- `name` (string, optional) — Human-readable name for the form. Rendered as the form's title.
- `description` (string, optional) — Longer description rendered below the title.
- `can_update` (boolean, optional, default: false) — If true, the recipient can reopen the submission URL and edit their answers. When false (default), the submission URL becomes read-only (`Already submitted`) after the first submit.
- `expires_at` (string, optional) — After this timestamp the form stops accepting submissions. Omit for no expiration.
- `theme_override` (FormThemeOverride, optional, nullable) — Per-form theme tweaks layered on top of your team's default theme. Omit or set to null to use team defaults.

### SendFormViaSmsRequestForm1

The minimum shape needed to create a form. Used by `POST /forms` and inline by `POST /forms/send`.

- `fields` (list of FormField, required) — Ordered list of fields the recipient will fill out. At least one field is required.
- `name` (string, optional) — Human-readable name for the form. Rendered as the form's title.
- `description` (string, optional) — Longer description rendered below the title.
- `can_update` (boolean, optional, default: false) — If true, the recipient can reopen the submission URL and edit their answers. When false (default), the submission URL becomes read-only (`Already submitted`) after the first submit.
- `expires_at` (string, optional) — After this timestamp the form stops accepting submissions. Omit for no expiration.
- `theme_override` (FormThemeOverride, optional, nullable) — Per-form theme tweaks layered on top of your team's default theme. Omit or set to null to use team defaults.

### FormField

A single field definition inside a form. Discriminated by `type`.

- `type`: `text` (text)
  - `key` (string, required) — Programmatic key used as the property name in the submission payload. Must start with a letter or underscore; letters, digits, and underscores only.
  - `label` (string, required) — Text shown above the input.
  - `help_text` (string, optional) — Small caption rendered under the input to give the submitter extra context.
  - `max_length` (integer, optional) — Maximum number of characters the submitter may enter.
  - `min_length` (integer, optional) — Minimum number of characters the submitter must enter.
  - `pattern` (string, optional) — Regex the value must match. Validated server-side with RE2, so patterns that enable catastrophic backtracking (backreferences, lookarounds) are rejected at form creation.
  - `placeholder` (string, optional) — Ghost text shown inside the input while it's empty.
  - `required` (boolean, optional, default: false) — When true, the submitter must provide a value. Default false.
- `type`: `textarea` (textarea)
  - `key` (string, required) — Programmatic key used as the property name in the submission payload. Must start with a letter or underscore; letters, digits, and underscores only.
  - `label` (string, required) — Text shown above the input.
  - `help_text` (string, optional) — Small caption rendered under the input to give the submitter extra context.
  - `max_length` (integer, optional) — Maximum number of characters the submitter may enter.
  - `min_length` (integer, optional) — Minimum number of characters the submitter must enter.
  - `pattern` (string, optional) — Regex the value must match. Same RE2 safety check as `TextField.pattern`.
  - `placeholder` (string, optional) — Ghost text shown inside the input while it's empty.
  - `required` (boolean, optional, default: false) — When true, the submitter must provide a value. Default false.
- `type`: `address` (address)
  - `key` (string, required) — Programmatic key used as the property name in the submission payload. Must start with a letter or underscore; letters, digits, and underscores only.
  - `label` (string, required) — Text shown above the input.
  - `help_text` (string, optional) — Small caption rendered under the input to give the submitter extra context.
  - `max_length` (integer, optional) — Maximum number of characters in the selected address.
  - `min_length` (integer, optional) — Minimum number of characters in the selected address.
  - `pattern` (string, optional) — Regex the value must match. Same RE2 safety check as `TextField.pattern`. Rarely needed for addresses — usually left off.
  - `placeholder` (string, optional) — Ghost text shown inside the input while it's empty.
  - `required` (boolean, optional, default: false) — When true, the submitter must provide a value. Default false.
- `type`: `email` (email)
  - `key` (string, required) — Programmatic key used as the property name in the submission payload. Must start with a letter or underscore; letters, digits, and underscores only.
  - `label` (string, required) — Text shown above the input.
  - `help_text` (string, optional) — Small caption rendered under the input to give the submitter extra context.
  - `placeholder` (string, optional) — Ghost text shown inside the input while it's empty.
  - `required` (boolean, optional, default: false) — When true, the submitter must provide a value. Default false.
- `type`: `url` (url)
  - `key` (string, required) — Programmatic key used as the property name in the submission payload. Must start with a letter or underscore; letters, digits, and underscores only.
  - `label` (string, required) — Text shown above the input.
  - `help_text` (string, optional) — Small caption rendered under the input to give the submitter extra context.
  - `placeholder` (string, optional) — Ghost text shown inside the input while it's empty.
  - `required` (boolean, optional, default: false) — When true, the submitter must provide a value. Default false.
- `type`: `phone` (phone)
  - `key` (string, required) — Programmatic key used as the property name in the submission payload. Must start with a letter or underscore; letters, digits, and underscores only.
  - `label` (string, required) — Text shown above the input.
  - `help_text` (string, optional) — Small caption rendered under the input to give the submitter extra context.
  - `placeholder` (string, optional) — Ghost text shown inside the input while it's empty.
  - `required` (boolean, optional, default: false) — When true, the submitter must provide a value. Default false.
- `type`: `number` (number)
  - `key` (string, required) — Programmatic key used as the property name in the submission payload. Must start with a letter or underscore; letters, digits, and underscores only.
  - `label` (string, required) — Text shown above the input.
  - `help_text` (string, optional) — Small caption rendered under the input to give the submitter extra context.
  - `max` (double, optional) — Maximum accepted value.
  - `min` (double, optional) — Minimum accepted value.
  - `placeholder` (string, optional) — Ghost text shown inside the input while it's empty.
  - `required` (boolean, optional, default: false) — When true, the submitter must provide a value. Default false.
  - `step` (double, optional) — Increment between valid values. For example, `step: 0.5` lets the user enter 1, 1.5, 2, etc.
- `type`: `range` (range)
  - `key` (string, required) — Programmatic key used as the property name in the submission payload. Must start with a letter or underscore; letters, digits, and underscores only.
  - `label` (string, required) — Text shown above the input.
  - `help_text` (string, optional) — Small caption rendered under the input to give the submitter extra context.
  - `max` (double, optional, default: 100) — Upper bound of the slider.
  - `min` (double, optional, default: 0) — Lower bound of the slider.
  - `required` (boolean, optional, default: false) — When true, the submitter must provide a value. Default false.
  - `step` (double, optional) — Increment between adjacent slider stops.
- `type`: `rating` (rating)
  - `key` (string, required) — Programmatic key used as the property name in the submission payload. Must start with a letter or underscore; letters, digits, and underscores only.
  - `label` (string, required) — Text shown above the input.
  - `help_text` (string, optional) — Small caption rendered under the input to give the submitter extra context.
  - `max` (integer, optional, default: 5) — Number of stars rendered.
  - `min` (integer, optional, default: 0) — Minimum allowed rating. `0` means the submitter can clear their selection.
  - `required` (boolean, optional, default: false) — When true, the submitter must provide a value. Default false.
- `type`: `date` (date)
  - `key` (string, required) — Programmatic key used as the property name in the submission payload. Must start with a letter or underscore; letters, digits, and underscores only.
  - `label` (string, required) — Text shown above the input.
  - `help_text` (string, optional) — Small caption rendered under the input to give the submitter extra context.
  - `max` (string, optional) — Latest selectable date as `YYYY-MM-DD`.
  - `min` (string, optional) — Earliest selectable date as `YYYY-MM-DD`.
  - `placeholder` (string, optional) — Ghost text shown inside the input while it's empty.
  - `required` (boolean, optional, default: false) — When true, the submitter must provide a value. Default false.
- `type`: `time` (time)
  - `key` (string, required) — Programmatic key used as the property name in the submission payload. Must start with a letter or underscore; letters, digits, and underscores only.
  - `label` (string, required) — Text shown above the input.
  - `help_text` (string, optional) — Small caption rendered under the input to give the submitter extra context.
  - `max` (string, optional) — Latest selectable time as `HH:MM` (24-hour).
  - `min` (string, optional) — Earliest selectable time as `HH:MM` (24-hour).
  - `placeholder` (string, optional) — Ghost text shown inside the input while it's empty.
  - `required` (boolean, optional, default: false) — When true, the submitter must provide a value. Default false.
- `type`: `datetime` (datetime)
  - `key` (string, required) — Programmatic key used as the property name in the submission payload. Must start with a letter or underscore; letters, digits, and underscores only.
  - `label` (string, required) — Text shown above the input.
  - `help_text` (string, optional) — Small caption rendered under the input to give the submitter extra context.
  - `max` (string, optional) — Latest selectable instant as an ISO 8601 timestamp.
  - `min` (string, optional) — Earliest selectable instant as an ISO 8601 timestamp.
  - `placeholder` (string, optional) — Ghost text shown inside the input while it's empty.
  - `required` (boolean, optional, default: false) — When true, the submitter must provide a value. Default false.
- `type`: `color` (color)
  - `key` (string, required) — Programmatic key used as the property name in the submission payload. Must start with a letter or underscore; letters, digits, and underscores only.
  - `label` (string, required) — Text shown above the input.
  - `help_text` (string, optional) — Small caption rendered under the input to give the submitter extra context.
  - `required` (boolean, optional, default: false) — When true, the submitter must provide a value. Default false.
- `type`: `select` (select)
  - `key` (string, required) — Programmatic key used as the property name in the submission payload. Must start with a letter or underscore; letters, digits, and underscores only.
  - `label` (string, required) — Text shown above the input.
  - `options` (list of FormFieldOption, required) — Choices shown in the dropdown. At least one is required.
  - `help_text` (string, optional) — Small caption rendered under the input to give the submitter extra context.
  - `placeholder` (string, optional) — Placeholder row shown when no option is selected (e.g. "Select…").
  - `required` (boolean, optional, default: false) — When true, the submitter must provide a value. Default false.
- `type`: `radio` (radio)
  - `key` (string, required) — Programmatic key used as the property name in the submission payload. Must start with a letter or underscore; letters, digits, and underscores only.
  - `label` (string, required) — Text shown above the input.
  - `options` (list of FormFieldOption, required) — Choices shown as radio buttons. At least one is required.
  - `help_text` (string, optional) — Small caption rendered under the input to give the submitter extra context.
  - `required` (boolean, optional, default: false) — When true, the submitter must provide a value. Default false.
- `type`: `checkbox` (checkbox)
  - `key` (string, required) — Programmatic key used as the property name in the submission payload. Must start with a letter or underscore; letters, digits, and underscores only.
  - `label` (string, required) — Text shown above the input.
  - `help_text` (string, optional) — Small caption rendered under the input to give the submitter extra context.
  - `max_selected` (integer, optional) — Maximum number of boxes the submitter may check. Extra boxes become uninteractive once the cap is reached.
  - `min_selected` (integer, optional) — Minimum number of boxes the submitter must check.
  - `options` (list of FormFieldOption, optional) — Choices shown as checkboxes. Omit to render a single boolean checkbox.
  - `required` (boolean, optional, default: false) — When true, the submitter must provide a value. Default false.

### FormThemeOverride

Sparse override layered on top of your team's theme. Every field is optional; provided fields replace the corresponding team defaults at render time. URL fields must use `https://`.

- `logo_url` (string, optional, nullable) — Logo displayed at the top of the form. Square images render as an inline mark; wide landscape images render as a full-width banner.
- `favicon_url` (string, optional, nullable) — Favicon shown in the browser tab when the form page loads.
- `theme_mode` (enum, optional) — Rendering mode. `light` forces the light palette, `dark` forces dark, and `auto` follows the respondent's OS-level appearance setting.
  - Allowed values: `light`, `dark`, `auto`
- `colors` (FormThemeOverrideColors, optional) — Per-mode palette overrides. Each mode is an object with optional `primary`, `background`, and `text` hex colors. Omit a mode to inherit the team default for it.
- `font_family` (enum, optional) — Typeface stack. `system` uses the respondent's OS default sans-serif.
  - Allowed values: `inter`, `system`, `serif`, `mono`, `rounded`
- `corner_radius` (enum, optional) — Border-radius preset for cards, inputs, and buttons.
  - Allowed values: `sharp`, `rounded`, `pill`
- `content_alignment` (enum, optional) — Horizontal alignment for the form header (title, description, logo).
  - Allowed values: `left`, `center`, `right`
- `background` (FormBackground, optional) — Background layer for the page. Choose solid, gradient, image, or a built-in pattern.
- `submit_button_label` (string, optional) — Text shown on the submit button. Defaults to `Submit`.
- `success_message` (string, optional) — Message shown on the success panel after a successful submit (unless `redirect_url` is set, in which case the respondent is redirected).
- `redirect_url` (string, optional, nullable) — When set, the submitter is redirected here after a successful submit instead of seeing the success panel.
- `privacy_url` (string, optional, nullable) — Link target for the `Privacy` footer link. Omit or set to null to hide the link.
- `terms_url` (string, optional, nullable) — Link target for the `Terms` footer link. Omit or set to null to hide the link.
- `og_image_url` (string, optional, nullable) — Image shown when the form URL is shared in social / messaging link previews (Open Graph).
- `og_description` (string, optional, nullable) — Open Graph description used in link previews. Falls back to the form's `description` when unset.

### FormFieldOption

A single choice inside a `select`, `radio`, or `checkbox` field.

- `value` (string, required) — Machine value sent back in the submission payload.
- `label` (string, required) — Human label rendered in the form.

### FormThemeOverrideColors

Per-mode palette overrides. Each mode is an object with optional `primary`, `background`, and `text` hex colors. Omit a mode to inherit the team default for it.

- `light` (FormColorPalette, optional) — Palette used when the form renders in light mode.
- `dark` (FormColorPalette, optional) — Palette used when the form renders in dark mode.

### FormBackground

Background layer for the form page. Discriminated by `type`.

- `type`: `solid` (solid)
- `type`: `gradient` (gradient)
  - `dark` (FormGradient, required) — Gradient used when the form renders in dark mode.
  - `light` (FormGradient, required) — Gradient used when the form renders in light mode.
- `type`: `image` (image)
  - `blur` (boolean, required) — When true, blurs the background image.
  - `tint_opacity` (integer, required) — Darkening overlay intensity from 0 (no overlay) to 80 (heavy). Helps preserve text contrast over busy images.
  - `url` (string, required) — HTTPS URL of the background image. Must start with `https://`.
  - `zoom` (integer, required) — Scale percentage. 100 is native size; higher values crop the image tighter.
- `type`: `pattern` (pattern)
  - `preset` (enum, required) — Which built-in pattern to render.
    - Allowed values: `noise`, `dots`, `mesh`, `grid`, `waves`, `topo`

### FormColorPalette

Per-mode palette. All three colors are optional; any you omit fall back to the team default for that mode.

- `primary` (string, optional) — Accent color used for the submit button, focus rings, filled checkboxes, and other interactive surfaces.
- `background` (string, optional) — Page background color. Fallback solid color used by Safari chrome tinting and safe-area regions.
- `text` (string, optional) — Body text and label color.

### FormGradient

A single light- or dark-mode gradient stop pair.

- `from` (string, required) — Starting color of the gradient.
- `to` (string, required) — Ending color of the gradient.
- `angle` (enum, required) — Direction the gradient runs. `top-bottom` is vertical; `diagonal` runs top-left → bottom-right.
  - Allowed values: `top-bottom`, `diagonal`

## Examples

### RCS send with recipient

**Request**

```json
{
  "from": "agent_iM9wQcyBBjYn",
  "to": "+14155551234",
  "form": "form_Oy2n7iUoi9CJwUU6",
  "fallback": {
    "from": "+14155550000"
  },
  "options": {
    "webview_mode": "FULL"
  }
}
```

**Response**

```json
{
  "form": {
    "id": "form_abc123",
    "url": "https://forms.pinnacle.sh/form_abc123",
    "name": "Contact request",
    "description": null,
    "fields": [
      {
        "key": "full_name",
        "label": "Full name",
        "required": true,
        "type": "text"
      }
    ],
    "can_update": false,
    "expires_at": null,
    "theme_override": null,
    "submission_count": 0,
    "last_submitted_at": null,
    "archived_at": null,
    "created_at": "2026-04-24T00:00:00Z",
    "updated_at": "2026-04-24T00:00:00Z"
  },
  "submission": {
    "id": "fsub_xyz789",
    "url": "https://forms.pinnacle.sh/fsub_xyz789",
    "form_id": "form_abc123",
    "from": "agent_iM9wQcyBBjYn",
    "to": "+14155551234",
    "data": null,
    "ip_address": null,
    "user_agent": null,
    "submitted_at": null,
    "created_at": "2026-04-24T00:00:00Z"
  },
  "message_id": "msg_123"
}
```

**SDK Code**

```python RCS send with recipient
import requests

url = "https://api.pinnacle.sh/forms/send"

payload = {
    "from": "agent_iM9wQcyBBjYn",
    "to": "+14155551234",
    "form": "form_Oy2n7iUoi9CJwUU6",
    "fallback": { "from": "+14155550000" },
    "options": { "webview_mode": "FULL" }
}
headers = {
    "PINNACLE-API-KEY": "<apiKey>",
    "Content-Type": "application/json"
}

response = requests.post(url, json=payload, headers=headers)

print(response.json())
```

```javascript RCS send with recipient
const url = 'https://api.pinnacle.sh/forms/send';
const options = {
  method: 'POST',
  headers: {'PINNACLE-API-KEY': '<apiKey>', 'Content-Type': 'application/json'},
  body: '{"from":"agent_iM9wQcyBBjYn","to":"+14155551234","form":"form_Oy2n7iUoi9CJwUU6","fallback":{"from":"+14155550000"},"options":{"webview_mode":"FULL"}}'
};

try {
  const response = await fetch(url, options);
  const data = await response.json();
  console.log(data);
} catch (error) {
  console.error(error);
}
```

```go RCS send with recipient
package main

import (
	"fmt"
	"strings"
	"net/http"
	"io"
)

func main() {

	url := "https://api.pinnacle.sh/forms/send"

	payload := strings.NewReader("{\n  \"from\": \"agent_iM9wQcyBBjYn\",\n  \"to\": \"+14155551234\",\n  \"form\": \"form_Oy2n7iUoi9CJwUU6\",\n  \"fallback\": {\n    \"from\": \"+14155550000\"\n  },\n  \"options\": {\n    \"webview_mode\": \"FULL\"\n  }\n}")

	req, _ := http.NewRequest("POST", url, payload)

	req.Header.Add("PINNACLE-API-KEY", "<apiKey>")
	req.Header.Add("Content-Type", "application/json")

	res, _ := http.DefaultClient.Do(req)

	defer res.Body.Close()
	body, _ := io.ReadAll(res.Body)

	fmt.Println(res)
	fmt.Println(string(body))

}
```

```ruby RCS send with recipient
require 'uri'
require 'net/http'

url = URI("https://api.pinnacle.sh/forms/send")

http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true

request = Net::HTTP::Post.new(url)
request["PINNACLE-API-KEY"] = '<apiKey>'
request["Content-Type"] = 'application/json'
request.body = "{\n  \"from\": \"agent_iM9wQcyBBjYn\",\n  \"to\": \"+14155551234\",\n  \"form\": \"form_Oy2n7iUoi9CJwUU6\",\n  \"fallback\": {\n    \"from\": \"+14155550000\"\n  },\n  \"options\": {\n    \"webview_mode\": \"FULL\"\n  }\n}"

response = http.request(request)
puts response.read_body
```

```java RCS send with recipient
import com.mashape.unirest.http.HttpResponse;
import com.mashape.unirest.http.Unirest;

HttpResponse<String> response = Unirest.post("https://api.pinnacle.sh/forms/send")
  .header("PINNACLE-API-KEY", "<apiKey>")
  .header("Content-Type", "application/json")
  .body("{\n  \"from\": \"agent_iM9wQcyBBjYn\",\n  \"to\": \"+14155551234\",\n  \"form\": \"form_Oy2n7iUoi9CJwUU6\",\n  \"fallback\": {\n    \"from\": \"+14155550000\"\n  },\n  \"options\": {\n    \"webview_mode\": \"FULL\"\n  }\n}")
  .asString();
```

```php RCS send with recipient
<?php
require_once('vendor/autoload.php');

$client = new \GuzzleHttp\Client();

$response = $client->request('POST', 'https://api.pinnacle.sh/forms/send', [
  'body' => '{
  "from": "agent_iM9wQcyBBjYn",
  "to": "+14155551234",
  "form": "form_Oy2n7iUoi9CJwUU6",
  "fallback": {
    "from": "+14155550000"
  },
  "options": {
    "webview_mode": "FULL"
  }
}',
  'headers' => [
    'Content-Type' => 'application/json',
    'PINNACLE-API-KEY' => '<apiKey>',
  ],
]);

echo $response->getBody();
```

```csharp RCS send with recipient
using RestSharp;

var client = new RestClient("https://api.pinnacle.sh/forms/send");
var request = new RestRequest(Method.POST);
request.AddHeader("PINNACLE-API-KEY", "<apiKey>");
request.AddHeader("Content-Type", "application/json");
request.AddParameter("application/json", "{\n  \"from\": \"agent_iM9wQcyBBjYn\",\n  \"to\": \"+14155551234\",\n  \"form\": \"form_Oy2n7iUoi9CJwUU6\",\n  \"fallback\": {\n    \"from\": \"+14155550000\"\n  },\n  \"options\": {\n    \"webview_mode\": \"FULL\"\n  }\n}", ParameterType.RequestBody);
IRestResponse response = client.Execute(request);
```

```swift RCS send with recipient
import Foundation

let headers = [
  "PINNACLE-API-KEY": "<apiKey>",
  "Content-Type": "application/json"
]
let parameters = [
  "from": "agent_iM9wQcyBBjYn",
  "to": "+14155551234",
  "form": "form_Oy2n7iUoi9CJwUU6",
  "fallback": ["from": "+14155550000"],
  "options": ["webview_mode": "FULL"]
] as [String : Any]

let postData = JSONSerialization.data(withJSONObject: parameters, options: [])

let request = NSMutableURLRequest(url: NSURL(string: "https://api.pinnacle.sh/forms/send")! as URL,
                                        cachePolicy: .useProtocolCachePolicy,
                                    timeoutInterval: 10.0)
request.httpMethod = "POST"
request.allHTTPHeaderFields = headers
request.httpBody = postData as Data

let session = URLSession.shared
let dataTask = session.dataTask(with: request as URLRequest, completionHandler: { (data, response, error) -> Void in
  if (error != nil) {
    print(error as Any)
  } else {
    let httpResponse = response as? HTTPURLResponse
    print(httpResponse)
  }
})

dataTask.resume()
```

### URL-only mint (no recipient)

**Request**

```json
{
  "from": "agent_iM9wQcyBBjYn",
  "to": "+14155551234",
  "form": "form_Oy2n7iUoi9CJwUU6",
  "fallback": {
    "from": "+14155550000"
  },
  "options": {
    "webview_mode": "FULL"
  }
}
```

**Response**

```json
{
  "form": {
    "id": "form_abc123",
    "url": "https://forms.pinnacle.sh/form_abc123",
    "name": "Contact request",
    "description": null,
    "fields": [
      {
        "key": "full_name",
        "label": "Full name",
        "required": true,
        "type": "text"
      }
    ],
    "can_update": false,
    "expires_at": null,
    "theme_override": null,
    "submission_count": 0,
    "last_submitted_at": null,
    "archived_at": null,
    "created_at": "2026-04-24T00:00:00Z",
    "updated_at": "2026-04-24T00:00:00Z"
  },
  "submission": {
    "id": "fsub_xyz789",
    "url": "https://forms.pinnacle.sh/fsub_xyz789",
    "form_id": "form_abc123",
    "from": "+14155550000",
    "to": null,
    "data": null,
    "ip_address": null,
    "user_agent": null,
    "submitted_at": null,
    "created_at": "2026-04-24T00:00:00Z"
  },
  "message_id": null
}
```

**SDK Code**

```python URL-only mint (no recipient)
import requests

url = "https://api.pinnacle.sh/forms/send"

payload = {
    "from": "agent_iM9wQcyBBjYn",
    "to": "+14155551234",
    "form": "form_Oy2n7iUoi9CJwUU6",
    "fallback": { "from": "+14155550000" },
    "options": { "webview_mode": "FULL" }
}
headers = {
    "PINNACLE-API-KEY": "<apiKey>",
    "Content-Type": "application/json"
}

response = requests.post(url, json=payload, headers=headers)

print(response.json())
```

```javascript URL-only mint (no recipient)
const url = 'https://api.pinnacle.sh/forms/send';
const options = {
  method: 'POST',
  headers: {'PINNACLE-API-KEY': '<apiKey>', 'Content-Type': 'application/json'},
  body: '{"from":"agent_iM9wQcyBBjYn","to":"+14155551234","form":"form_Oy2n7iUoi9CJwUU6","fallback":{"from":"+14155550000"},"options":{"webview_mode":"FULL"}}'
};

try {
  const response = await fetch(url, options);
  const data = await response.json();
  console.log(data);
} catch (error) {
  console.error(error);
}
```

```go URL-only mint (no recipient)
package main

import (
	"fmt"
	"strings"
	"net/http"
	"io"
)

func main() {

	url := "https://api.pinnacle.sh/forms/send"

	payload := strings.NewReader("{\n  \"from\": \"agent_iM9wQcyBBjYn\",\n  \"to\": \"+14155551234\",\n  \"form\": \"form_Oy2n7iUoi9CJwUU6\",\n  \"fallback\": {\n    \"from\": \"+14155550000\"\n  },\n  \"options\": {\n    \"webview_mode\": \"FULL\"\n  }\n}")

	req, _ := http.NewRequest("POST", url, payload)

	req.Header.Add("PINNACLE-API-KEY", "<apiKey>")
	req.Header.Add("Content-Type", "application/json")

	res, _ := http.DefaultClient.Do(req)

	defer res.Body.Close()
	body, _ := io.ReadAll(res.Body)

	fmt.Println(res)
	fmt.Println(string(body))

}
```

```ruby URL-only mint (no recipient)
require 'uri'
require 'net/http'

url = URI("https://api.pinnacle.sh/forms/send")

http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true

request = Net::HTTP::Post.new(url)
request["PINNACLE-API-KEY"] = '<apiKey>'
request["Content-Type"] = 'application/json'
request.body = "{\n  \"from\": \"agent_iM9wQcyBBjYn\",\n  \"to\": \"+14155551234\",\n  \"form\": \"form_Oy2n7iUoi9CJwUU6\",\n  \"fallback\": {\n    \"from\": \"+14155550000\"\n  },\n  \"options\": {\n    \"webview_mode\": \"FULL\"\n  }\n}"

response = http.request(request)
puts response.read_body
```

```java URL-only mint (no recipient)
import com.mashape.unirest.http.HttpResponse;
import com.mashape.unirest.http.Unirest;

HttpResponse<String> response = Unirest.post("https://api.pinnacle.sh/forms/send")
  .header("PINNACLE-API-KEY", "<apiKey>")
  .header("Content-Type", "application/json")
  .body("{\n  \"from\": \"agent_iM9wQcyBBjYn\",\n  \"to\": \"+14155551234\",\n  \"form\": \"form_Oy2n7iUoi9CJwUU6\",\n  \"fallback\": {\n    \"from\": \"+14155550000\"\n  },\n  \"options\": {\n    \"webview_mode\": \"FULL\"\n  }\n}")
  .asString();
```

```php URL-only mint (no recipient)
<?php
require_once('vendor/autoload.php');

$client = new \GuzzleHttp\Client();

$response = $client->request('POST', 'https://api.pinnacle.sh/forms/send', [
  'body' => '{
  "from": "agent_iM9wQcyBBjYn",
  "to": "+14155551234",
  "form": "form_Oy2n7iUoi9CJwUU6",
  "fallback": {
    "from": "+14155550000"
  },
  "options": {
    "webview_mode": "FULL"
  }
}',
  'headers' => [
    'Content-Type' => 'application/json',
    'PINNACLE-API-KEY' => '<apiKey>',
  ],
]);

echo $response->getBody();
```

```csharp URL-only mint (no recipient)
using RestSharp;

var client = new RestClient("https://api.pinnacle.sh/forms/send");
var request = new RestRequest(Method.POST);
request.AddHeader("PINNACLE-API-KEY", "<apiKey>");
request.AddHeader("Content-Type", "application/json");
request.AddParameter("application/json", "{\n  \"from\": \"agent_iM9wQcyBBjYn\",\n  \"to\": \"+14155551234\",\n  \"form\": \"form_Oy2n7iUoi9CJwUU6\",\n  \"fallback\": {\n    \"from\": \"+14155550000\"\n  },\n  \"options\": {\n    \"webview_mode\": \"FULL\"\n  }\n}", ParameterType.RequestBody);
IRestResponse response = client.Execute(request);
```

```swift URL-only mint (no recipient)
import Foundation

let headers = [
  "PINNACLE-API-KEY": "<apiKey>",
  "Content-Type": "application/json"
]
let parameters = [
  "from": "agent_iM9wQcyBBjYn",
  "to": "+14155551234",
  "form": "form_Oy2n7iUoi9CJwUU6",
  "fallback": ["from": "+14155550000"],
  "options": ["webview_mode": "FULL"]
] as [String : Any]

let postData = JSONSerialization.data(withJSONObject: parameters, options: [])

let request = NSMutableURLRequest(url: NSURL(string: "https://api.pinnacle.sh/forms/send")! as URL,
                                        cachePolicy: .useProtocolCachePolicy,
                                    timeoutInterval: 10.0)
request.httpMethod = "POST"
request.allHTTPHeaderFields = headers
request.httpBody = postData as Data

let session = URLSession.shared
let dataTask = session.dataTask(with: request as URLRequest, completionHandler: { (data, response, error) -> Void in
  if (error != nil) {
    print(error as Any)
  } else {
    let httpResponse = response as? HTTPURLResponse
    print(httpResponse)
  }
})

dataTask.resume()
```