HomeGuidesAPI Reference
ChangelogHelp CenterCommunityContact Us
API Reference

Forms API overview (revision 2026-10-15.pre)

Create, retrieve, list, and update Web and In-App forms with the polymorphic form definition.

OpenAPI Spec

Machine-readable spec for the Forms API at revision 2026-10-15.pre: beta/categories/forms.json

Before you begin

Check out our general API overview to make sure you're ready to get started with specific endpoints.

🚧

Beta

The polymorphic form definition is in beta. Send the revision: 2026-10-15.pre header to use it. Endpoints may change before general availability. The generally available Web form contracts at 2026-10-15 and earlier don't change. See the Forms API overview for those.

At revision 2026-10-15.pre, the Forms API supports two kinds of forms:

  • Web forms appear on your website.
  • In-App forms appear inside your mobile app.

Both kinds share one form definition shape. You choose the kind with the type field on the definition.

❗️

Breaking change for beta callers

If you already call the Forms API with a 2026-10-15.pre revision header, the definition attribute has a new shape. You must wrap your existing versions array in an object that has a type of web. See Move an existing Web form integration to the beta shape.

Supported operations

OperationEndpointWeb formsIn-App forms
Create FormPOST /api/forms/YesYes (draft only)
Get FormGET /api/forms/{id}YesYes
Get FormsGET /api/forms/YesYes
Update FormPATCH /api/forms/{id}YesYes (draft versions only)
Publish FormPOST /api/form-publish-jobsYesYes
Unpublish FormPOST /api/form-unpublish-jobsYesYes

Keep the following limitations in mind:

  • Publish Form and Unpublish Form don't return a job that you can retrieve. To confirm the result, call Get Form and check the form's status. See Publish and unpublish a form.
  • You can't convert a form from one type to the other. A form keeps the type it was created with.
  • In-App forms don't support A/B tests. You can't create or edit an In-App A/B test with the API.
  • Create Form accepts exactly one version and one step for In-App forms.

Data model

A form has the following:

  • id

    The form ID.

  • attributes

    • name

      The name of the form.

    • status

      The current status of the form, e.g., draft or live.

    • ab_test

      Whether or not the form has an A/B test. This is always false for In-App forms.

    • created_at

      The timestamp of when the form was created.

    • updated_at

      The timestamp of when the form was last updated.

    • definition

      A definition of the form. It has the following fields:

      • type

        The kind of form: web or in-app. This value decides which fields each entry in versions accepts.

      • versions

        The versions of the form. Each version holds the form's layout, content, triggers, and targeting.

Web form definitions

When type is web, each entry in versions has the same fields as the definition.versions entries in the Forms API overview. Those fields are unchanged. The only difference is the type field next to versions.

ab_test stays a top-level attribute of the form for Web forms.

In-App form definitions

When type is in-app, each entry in versions has the following fields:

  • form_type

    The layout of the form: popup, fullscreen, or flyout.

  • location

    The position of a flyout: top_center or bottom_center. Required when form_type is flyout. Not allowed for other layouts.

  • steps

    The screens of the form. Each step has columns, each column has rows, and each row has blocks.

  • triggers

    When and how often the form appears. See Triggers.

  • targeting

    Optional. The audience for the version, as an allow_list or deny_list of list and segment IDs. If you include targeting, it must name at least one list or segment. If you omit it, the form can show to all profiles with the mobile app.

  • styles

    Optional. Styling for the form container, such as width, background_color, and padding. If you omit it, Klaviyo applies defaults.

Targeting belongs to each version, not to the definition.

Klaviyo generates the id of every version, step, column, row, and block. Don't send these IDs, or the name, status, or ab_test of a version, when you create a form. Create Form returns them in the response.

Blocks

Each block has a type. In-App forms support the following block types:

Block typeDescription
textText with basic HTML formatting.
imageAn image from a src URL or a Klaviyo image library asset_id. It can have an optional action.
buttonA button with a label and an action.
couponA static coupon code.
countdown_timerA countdown to a fixed end time, or for a fixed duration.
reviewA review that you supply. The review isn't looked up from your account.
custom_htmlCustom HTML. Your account must be allowed to use custom CSS and HTML.

A button's action has a type of close_form, deeplink_to_screen, or open_url:

  • For deeplink_to_screen, send deeplink_url. To send each platform to a different screen, set use_separate_deeplink_urls to true and send both deeplink_url_ios and deeplink_url_android instead.
  • For open_url, send web_url. The URL scheme must be http, https, mailto, tel, or sms.

Triggers

The triggers object controls when the form appears:

  • display_trigger

    What causes the form to appear. Set its type to one of the following:

    • app_open: The form appears when a profile opens your app.
    • performed_event: The form appears when a profile performs an event. Send the event_name. You can also send one entry in property_filters to match an event property exactly.
    • push_notification_open: The form appears when a profile opens a push notification. Send the message_title to match.
  • delay_seconds

    How long to wait after the trigger, in seconds, up to 86400. The default is 0.

  • timeout_days

    The minimum number of days before the form shows to the same profile again. The default is 7.

  • suppress_after_dismiss

    Whether to stop showing the form to a profile after they dismiss it. The default is true.

Create a form

Create a draft form with the Create Form endpoint. POST /api/forms/

All forms are created in a draft status. The status attribute is optional and defaults to draft, so you can omit it. Sending live returns a 400 error. Send a definition with a type of web or in-app. A successful request returns a 201 and the form's definition, including the IDs that Klaviyo generated.

Create an In-App form

The following request creates an In-App popup with a text block and a button. The popup appears 5 seconds after a profile opens your app.

curl --request POST \
     --url https://a.klaviyo.com/api/forms/ \
     --header 'Authorization: Klaviyo-API-Key your-private-api-key' \
     --header 'accept: application/vnd.api+json' \
     --header 'content-type: application/vnd.api+json' \
     --header 'revision: 2026-10-15.pre' \
     --data '
{
  "data": {
    "type": "form",
    "attributes": {
      "name": "Welcome offer",
      "definition": {
        "type": "in-app",
        "versions": [
          {
            "form_type": "popup",
            "triggers": {
              "display_trigger": {
                "type": "app_open"
              },
              "delay_seconds": 5,
              "timeout_days": 7,
              "suppress_after_dismiss": true
            },
            "steps": [
              {
                "columns": [
                  {
                    "rows": [
                      {
                        "blocks": [
                          {
                            "type": "text",
                            "content": "<p>Get 10% off your first order</p>"
                          }
                        ]
                      },
                      {
                        "blocks": [
                          {
                            "type": "button",
                            "label": "Shop now",
                            "action": {
                              "type": "deeplink_to_screen",
                              "deeplink_url": "myapp://screen/shop"
                            }
                          }
                        ]
                      }
                    ]
                  }
                ]
              }
            ]
          }
        ]
      }
    }
  }
}
'
{
  "data": {
    "type": "form",
    "id": "Ser5Vq",
    "attributes": {
      "status": "draft",
      "ab_test": false,
      "name": "Welcome offer",
      "created_at": "2026-10-22T15:50:35.794587+00:00",
      "updated_at": "2026-10-22T15:50:35.794606+00:00",
      "definition": {
        "type": "in-app",
        "versions": [
          {
            "form_type": "popup",
            "triggers": {
              "display_trigger": {
                "type": "app_open"
              },
              "delay_seconds": 5,
              "timeout_days": 7,
              "suppress_after_dismiss": true
            },
            "steps": [
              {
                "columns": [
                  {
                    "rows": [
                      {
                        "blocks": [
                          {
                            "type": "text",
                            "content": "<p>Get 10% off your first order</p>",
                            "styles": {
                              "padding": {
                                "left": 6,
                                "right": 6,
                                "top": 10,
                                "bottom": 10
                              },
                              "background_color": null
                            },
                            "id": "01M5J4X71KGDNK3R34WVM2ASKF"
                          }
                        ],
                        "id": "01M5J4X71G2B9E9XEZH6PNGFW9"
                      },
                      {
                        "blocks": [
                          {
                            "type": "button",
                            "label": "Shop now",
                            "action": {
                              "type": "deeplink_to_screen",
                              "deeplink_url": "myapp://screen/shop",
                              "deeplink_url_ios": null,
                              "deeplink_url_android": null,
                              "use_separate_deeplink_urls": null,
                              "web_url": null,
                              "id": "01M5J4X7207AX231JGK29DN5W1"
                            },
                            "styles": {
                              "background_color": null,
                              "padding": {
                                "left": 6,
                                "right": 6,
                                "top": 10,
                                "bottom": 10
                              },
                              "inner_padding": null,
                              "text_styles": {
                                "font_family": "Arial, 'Helvetica Neue', Helvetica, sans-serif",
                                "font_size": 16,
                                "font_weight": 700,
                                "text_color": "#FFFFFF",
                                "character_spacing": 0,
                                "font_style": null,
                                "text_decoration": null
                              },
                              "color": null,
                              "height": 50,
                              "width": "fill",
                              "border_styles": {
                                "radius": 2,
                                "color": null,
                                "style": null,
                                "thickness": null
                              },
                              "alignment": "center",
                              "drop_shadow": {
                                "enabled": false,
                                "color": "#000000",
                                "blur": 15,
                                "x_offset": 0,
                                "y_offset": 0
                              }
                            },
                            "id": "01M5J4X71X5YBRP8VFRKD27RVW"
                          }
                        ],
                        "id": "01M5J4X71T3XMX546HRB6SGCXW"
                      }
                    ],
                    "id": "01M5J4X71BRNERMH6R5QSVJNG0"
                  }
                ],
                "id": "01M5J4X715NF91ECHP9H6Z8S3S",
                "name": "Main"
              }
            ],
            "location": null,
            "targeting": null,
            "styles": {
              "width": "medium",
              "custom_width": null,
              "minimum_height": 250,
              "background_color": "#FFFFFF",
              "background_image": null,
              "overlay_color": "rgba(20,20,20,0.6)",
              "drop_shadow": {
                "enabled": false,
                "blur": 30,
                "color": "rgba(0,0,0,0.15)"
              },
              "border_styles": {
                "radius": 2,
                "color": "#000000",
                "style": null,
                "thickness": 0
              },
              "padding": {
                "left": 20,
                "right": 20,
                "top": 20,
                "bottom": 20
              },
              "margin": {
                "left": 0,
                "right": 0,
                "top": 0,
                "bottom": 0
              },
              "close_button": {
                "background_color": "rgba(180, 187, 195, 0.65)",
                "outline_color": "#FFFFFF",
                "color": "#FFFFFF",
                "stroke": 2.0,
                "size": 20,
                "margin": {
                  "left": 8,
                  "right": 8,
                  "top": 8,
                  "bottom": 8
                }
              },
              "custom_css": {
                "enabled": false,
                "css": ""
              }
            },
            "ab_test": false,
            "name": "Default",
            "id": 29155010,
            "status": "draft"
          }
        ]
      }
    },
    "links": {
      "self": "https://a.klaviyo.com/api/forms/Ser5Vq/"
    }
  },
  "links": {
    "self": "https://a.klaviyo.com/api/forms/"
  }
}

Keep the following in mind when you create an In-App form:

  • Send exactly one version with exactly one step.
  • Send your content in steps[].columns. A columns shorthand directly on the version isn't supported.
  • Send location for a flyout, and omit it for other layouts. The full width style is only supported for a flyout.
  • Send ab_test as false or omit it, both on the form and on the version.
  • A static coupon text and a fixed countdown end_time can be null in a draft. A fixed countdown end_time must be in the future when you send it. Image blocks must have a complete image.
  • Klaviyo names a new version Default and a new step Main. You can't set names when you create a form.
  • If a request is invalid, the 400 error's source.pointer points to the field to fix, for example /data/attributes/definition/versions/0/location.

Create a Web form

To create a Web form, set the definition's type to web and put your existing versions next to it. Keep ab_test and status as top-level attributes. The following request creates a simple sign-up form with a sign-up step and a success step. A Web form version needs at least two steps. For a complete Web form body, see Create a form in the Forms API overview.

curl --request POST \
     --url https://a.klaviyo.com/api/forms/ \
     --header 'Authorization: Klaviyo-API-Key your-private-api-key' \
     --header 'accept: application/vnd.api+json' \
     --header 'content-type: application/vnd.api+json' \
     --header 'revision: 2026-10-15.pre' \
     --data '
{
  "data": {
    "type": "form",
    "attributes": {
      "name": "My first API form",
      "status": "draft",
      "ab_test": false,
      "definition": {
        "type": "web",
        "versions": [
          {
            "steps": [
              {
                "columns": [
                  {
                    "rows": [
                      {
                        "blocks": [
                          {
                            "type": "html_text",
                            "properties": {
                              "content": "<div>Sign up for our newsletter</div>"
                            }
                          }
                        ]
                      },
                      {
                        "blocks": [
                          {
                            "type": "email",
                            "properties": {
                              "label": "Email",
                              "placeholder": "Enter your email address",
                              "required": true
                            }
                          }
                        ]
                      },
                      {
                        "blocks": [
                          {
                            "type": "button",
                            "properties": {
                              "label": "Sign up"
                            },
                            "action": {
                              "submit": true,
                              "type": "next_step",
                              "properties": {
                                "list_id": "{SOME_LIST_ID}"
                              }
                            }
                          }
                        ]
                      }
                    ]
                  }
                ],
                "name": "email sign-up"
              },
              {
                "columns": [
                  {
                    "rows": [
                      {
                        "blocks": [
                          {
                            "type": "html_text",
                            "properties": {
                              "content": "<div>Thank you for subscribing!</div>"
                            }
                          }
                        ]
                      }
                    ]
                  }
                ],
                "name": "success step"
              }
            ]
          }
        ]
      }
    }
  }
}
'

Move an existing Web form integration to the beta shape

If your integration already creates or reads Web forms at a 2026-10-15.pre revision, make the following changes:

  1. Add a type of web to definition, next to versions. The definition object you send has two keys, type and versions, where versions holds the same array you sent before.
  2. Read definition.type and definition.versions from responses.
  3. Leave ab_test and status where they are, as top-level attributes of the form.

Requests at 2026-10-15 and earlier revisions keep their existing shapes.

Get a form

Retrieve a form with the Get Form endpoint. GET /api/forms/{id}

The response has the same shape as the Create Form response, with definition.type set to web or in-app.

For a Web form, definition.type is web and versions is unchanged from the Forms API overview. The following response is trimmed to a single text block to show the new type field. A real response includes all of the version's default properties and styles.

{
  "data": {
    "type": "form",
    "id": "Ser5Vq",
    "attributes": {
      "status": "draft",
      "ab_test": false,
      "name": "My first API form",
      "created_at": "2026-10-22T15:59:04+00:00",
      "updated_at": "2026-10-22T15:59:04+00:00",
      "definition": {
        "type": "web",
        "versions": [
          {
            "id": 29155217,
            "type": "popup",
            "channel": "WEB",
            "status": "draft",
            "ab_test": false,
            "steps": [
              {
                "id": "01JTP6F3EERYR2T30C4ZDCS6JA",
                "columns": [
                  {
                    "id": "01JTP6F3EQ9TQ6TEWQSV33JDQ7",
                    "rows": [
                      {
                        "id": "01JTP6F3EZRM5M0HM42NPC6QA5",
                        "blocks": [
                          {
                            "id": "01JTP6F3F52N3WXF6MNNSH1MQ4",
                            "type": "html_text",
                            "properties": {
                              "content": "<div>Sign up for our newsletter</div>"
                            }
                          }
                        ]
                      }
                    ]
                  }
                ]
              }
            ]
          }
        ]
      }
    },
    "links": {
      "self": "https://a.klaviyo.com/api/forms/Ser5Vq"
    }
  }
}

Get forms

List the forms in your account with the Get Forms endpoint. GET /api/forms/

At this revision, the list includes both Web and In-App forms, each with its definition. To list only one kind, filter on channel_type:

curl --request GET \
     --url 'https://a.klaviyo.com/api/forms/?filter=equals(channel_type,"in-app")' \
     --header 'Authorization: Klaviyo-API-Key your-private-api-key' \
     --header 'accept: application/vnd.api+json' \
     --header 'revision: 2026-10-15.pre'

The channel_type filter accepts web or in-app with the equals operator. Any other value returns a 400 error.

Update a form

Edit a form's draft with the Update Form endpoint. PATCH /api/forms/{id}

You can update a form's name and definition. Attributes you omit keep their current values. Neither can be null. Any other attribute, such as status or ab_test, returns a 400 error that points to the field.

When you send a definition, it replaces the editable definition entirely, so use the same workflow as the Forms API overview:

  1. Retrieve the form with GET /api/forms/{id}.
  2. Modify the draft versions in the returned definition. Keep every version and its id.
  3. Send the full definition in your Update Form request.

The following rules apply to updates:

  • The definition's type can't change, for either Web or In-App forms. Send the same type that Get Form returned. Changing it returns a 400 error that points to definition/type.
  • Send each existing version id exactly once. You can't add or remove versions. If a live form has a live and a draft version, send both.
  • You can only edit draft versions. Return each live version exactly as you received it. Changing a live version returns a 400 error.
  • Anything you leave out of a draft version, such as a block or a row, is deleted from the form. This applies to both Web and In-App forms.
  • Update Form doesn't check whether the form changed since you retrieved it. If you send a definition from an earlier Get Form call, it can overwrite edits made in the meantime. Retrieve the form again right before you update it.

The following request renames a form and leaves its definition as it is:

curl --request PATCH \
     --url https://a.klaviyo.com/api/forms/Ser5Vq \
     --header 'Authorization: Klaviyo-API-Key your-private-api-key' \
     --header 'accept: application/vnd.api+json' \
     --header 'content-type: application/vnd.api+json' \
     --header 'revision: 2026-10-15.pre' \
     --data '
{
  "data": {
    "type": "form",
    "id": "Ser5Vq",
    "attributes": {
      "name": "Welcome offer (renamed)"
    }
  }
}
'

The following request changes the text of the In-App popup from Create an In-App form to "Get 15% off your first order". It sends the whole definition, with the version, step, column, row, and block IDs that Get Form returned, and with only the text block's content changed:

curl --request PATCH \
     --url https://a.klaviyo.com/api/forms/Ser5Vq \
     --header 'Authorization: Klaviyo-API-Key your-private-api-key' \
     --header 'accept: application/vnd.api+json' \
     --header 'content-type: application/vnd.api+json' \
     --header 'revision: 2026-10-15.pre' \
     --data '
{
  "data": {
    "type": "form",
    "id": "Ser5Vq",
    "attributes": {
      "definition": {
        "type": "in-app",
        "versions": [
          {
            "form_type": "popup",
            "triggers": {
              "display_trigger": {
                "type": "app_open"
              },
              "delay_seconds": 5,
              "timeout_days": 7,
              "suppress_after_dismiss": true
            },
            "steps": [
              {
                "columns": [
                  {
                    "rows": [
                      {
                        "blocks": [
                          {
                            "type": "text",
                            "content": "<p>Get 15% off your first order</p>",
                            "styles": {
                              "padding": {
                                "left": 6,
                                "right": 6,
                                "top": 10,
                                "bottom": 10
                              },
                              "background_color": null
                            },
                            "id": "01M5J4X71KGDNK3R34WVM2ASKF"
                          }
                        ],
                        "id": "01M5J4X71G2B9E9XEZH6PNGFW9"
                      },
                      {
                        "blocks": [
                          {
                            "type": "button",
                            "label": "Shop now",
                            "action": {
                              "type": "deeplink_to_screen",
                              "deeplink_url": "myapp://screen/shop",
                              "deeplink_url_ios": null,
                              "deeplink_url_android": null,
                              "use_separate_deeplink_urls": null,
                              "web_url": null,
                              "id": "01M5J4X7207AX231JGK29DN5W1"
                            },
                            "styles": {
                              "background_color": null,
                              "padding": {
                                "left": 6,
                                "right": 6,
                                "top": 10,
                                "bottom": 10
                              },
                              "inner_padding": null,
                              "text_styles": {
                                "font_family": "Arial, 'Helvetica Neue', Helvetica, sans-serif",
                                "font_size": 16,
                                "font_weight": 700,
                                "text_color": "#FFFFFF",
                                "character_spacing": 0,
                                "font_style": null,
                                "text_decoration": null
                              },
                              "color": null,
                              "height": 50,
                              "width": "fill",
                              "border_styles": {
                                "radius": 2,
                                "color": null,
                                "style": null,
                                "thickness": null
                              },
                              "alignment": "center",
                              "drop_shadow": {
                                "enabled": false,
                                "color": "#000000",
                                "blur": 15,
                                "x_offset": 0,
                                "y_offset": 0
                              }
                            },
                            "id": "01M5J4X71X5YBRP8VFRKD27RVW"
                          }
                        ],
                        "id": "01M5J4X71T3XMX546HRB6SGCXW"
                      }
                    ],
                    "id": "01M5J4X71BRNERMH6R5QSVJNG0"
                  }
                ],
                "id": "01M5J4X715NF91ECHP9H6Z8S3S",
                "name": "Main"
              }
            ],
            "location": null,
            "targeting": null,
            "styles": {
              "width": "medium",
              "custom_width": null,
              "minimum_height": 250,
              "background_color": "#FFFFFF",
              "background_image": null,
              "overlay_color": "rgba(20,20,20,0.6)",
              "drop_shadow": {
                "enabled": false,
                "blur": 30,
                "color": "rgba(0,0,0,0.15)"
              },
              "border_styles": {
                "radius": 2,
                "color": "#000000",
                "style": null,
                "thickness": 0
              },
              "padding": {
                "left": 20,
                "right": 20,
                "top": 20,
                "bottom": 20
              },
              "margin": {
                "left": 0,
                "right": 0,
                "top": 0,
                "bottom": 0
              },
              "close_button": {
                "background_color": "rgba(180, 187, 195, 0.65)",
                "outline_color": "#FFFFFF",
                "color": "#FFFFFF",
                "stroke": 2.0,
                "size": 20,
                "margin": {
                  "left": 8,
                  "right": 8,
                  "top": 8,
                  "bottom": 8
                }
              },
              "custom_css": {
                "enabled": false,
                "css": ""
              }
            },
            "ab_test": false,
            "name": "Default",
            "id": 29155010,
            "status": "draft"
          }
        ]
      }
    }
  }
}
'

A successful request returns a 200 and the updated form, in the same shape as the Get Form response.

Your changes stay in draft until you publish the form.

Publish and unpublish a form

Publish a form's draft version with the Publish Form endpoint, and take a live form down with the Unpublish Form endpoint. Both endpoints work for Web and In-App forms. Their request and response behavior is described in the Forms API overview.

curl --request POST \
     --url https://a.klaviyo.com/api/form-publish-jobs/ \
     --header 'Authorization: Klaviyo-API-Key your-private-api-key' \
     --header 'accept: application/vnd.api+json' \
     --header 'content-type: application/vnd.api+json' \
     --header 'revision: 2026-10-15.pre' \
     --data '
{
  "data": {
    "type": "form-publish-job",
    "relationships": {
      "form": {
        "data": {
          "type": "form",
          "id": "Ser5Vq"
        }
      }
    }
  }
}
'

To unpublish, send the same request body to POST /api/form-unpublish-jobs/ with the type form-unpublish-job.

Keep the following in mind for In-App forms:

  • After you publish an In-App form, Get Form returns two versions: the live version and a draft version. You edit the draft version, and the live version stays unchanged until you publish again.
  • After you unpublish, the form returns to draft and only the draft version remains.
  • Unpublishing a form that isn't live succeeds without making changes, so you can safely retry it.
  • The response contains a job ID, but you can't retrieve the job. Call Get Form to confirm the form's status.

Next steps

Additional resources