# Validate Workflow Definition

POST https://services.leadconnectorhq.com/workflows/definition/validate

Run the full save pipeline against a definition and report what a real save would do. Nothing is persisted. Returns 200 with `valid: false` when the definition is rejected — a 4xx here means the request itself was malformed.

## Authentication

Send `Authorization: Bearer <token>` using:

- **Sub-Account token: OAuth access token or Private Integration Token.** Scopes: `workflows.write`

## Parameters

### Header Parameters

| Name | Type | Required | Description |
|------|------|----------|-------------|
| `Version` | string | Yes | API Version |

### Query Parameters

| Name | Type | Required | Description |
|------|------|----------|-------------|
| `locationId` | string | Yes | Sub-account (location) ID the workflow belongs to |

## OpenAPI Specification

```json
{
  "openapi": "3.0.0",
  "info": {
    "title": "HighLevel API",
    "version": "v4"
  },
  "servers": [
    {
      "url": "https://services.leadconnectorhq.com"
    }
  ],
  "paths": {
    "/workflows/definition/validate": {
      "post": {
        "summary": "Validate Workflow Definition",
        "operationId": "validateWorkflowDefinition",
        "description": "Run the full save pipeline against a definition and report what a real save would do. Nothing is persisted. Returns 200 with `valid: false` when the definition is rejected — a 4xx here means the request itself was malformed.",
        "parameters": [
          {
            "name": "Version",
            "in": "header",
            "description": "API Version",
            "required": true,
            "schema": {
              "type": "string",
              "enum": [
                "v4"
              ]
            }
          },
          {
            "name": "locationId",
            "required": true,
            "in": "query",
            "description": "Sub-account (location) ID the workflow belongs to",
            "schema": {
              "example": "ve9EPM428h8vShlRW1KT",
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ValidateWorkflowDefinitionDto"
              },
              "examples": {
                "linear": {
                  "summary": "1. Linear — two steps in sequence",
                  "description": "The smallest useful workflow: a trigger, two actions, and one edge joining them. `edges` is what orders the steps — `actions` is an unordered set, so a definition with no edges is a pile of disconnected steps, not a sequence.\n\nNote `tags`: asset references are resolved by name against the sub-account, so the tag must already exist there or the save is rejected with `ASSET_TAG_NOT_FOUND`. The same applies to forms, calendars, users and custom fields.",
                  "value": {
                    "version": 1,
                    "name": "Welcome new contacts",
                    "source": {
                      "system": "api"
                    },
                    "triggers": [
                      {
                        "type": "contact_created",
                        "name": "New contact"
                      }
                    ],
                    "actions": [
                      {
                        "id": "send-welcome-sms",
                        "type": "sms",
                        "name": "Send welcome SMS",
                        "attributes": {
                          "body": "Hi {{contact.first_name}}, thanks for getting in touch!"
                        }
                      },
                      {
                        "id": "tag-as-welcomed",
                        "type": "add_contact_tag",
                        "name": "Tag as welcomed",
                        "attributes": {
                          "tags": [
                            "your-existing-tag"
                          ]
                        }
                      }
                    ],
                    "edges": [
                      {
                        "from": "send-welcome-sms",
                        "to": "tag-as-welcomed"
                      }
                    ]
                  }
                },
                "branching": {
                  "summary": "2. If/else — two lanes from one condition",
                  "description": "A branch is an `if_else` action plus edges carrying a `branch` label. `branch: \"yes\"` is the matching lane and `branch: \"default\"` is the else lane; an `if_else` with no `yes` edge returns a warning, because its matching lane would be empty.\n\n`conditionName`, `operator` and `if` are required. The condition payload itself (`segments`, or `branches` on version 2) passes through **unvalidated** — the API cannot check it, so build one branch in the UI and read it back with Get Workflow Definition rather than hand-writing the condition internals.",
                  "value": {
                    "version": 1,
                    "name": "Route by tag",
                    "source": {
                      "system": "api"
                    },
                    "triggers": [
                      {
                        "type": "contact_created"
                      }
                    ],
                    "actions": [
                      {
                        "id": "is-vip",
                        "type": "if_else",
                        "name": "Is this a VIP?",
                        "attributes": {
                          "conditionName": "Is this a VIP?",
                          "operator": "and",
                          "if": true
                        }
                      },
                      {
                        "id": "vip-greeting",
                        "type": "sms",
                        "name": "VIP greeting",
                        "attributes": {
                          "body": "Welcome back!"
                        }
                      },
                      {
                        "id": "standard-greeting",
                        "type": "sms",
                        "name": "Standard greeting",
                        "attributes": {
                          "body": "Welcome!"
                        }
                      }
                    ],
                    "edges": [
                      {
                        "from": "is-vip",
                        "to": "vip-greeting",
                        "branch": "yes"
                      },
                      {
                        "from": "is-vip",
                        "to": "standard-greeting",
                        "branch": "default"
                      }
                    ]
                  }
                },
                "wait": {
                  "summary": "3. Wait — pause before the next step",
                  "description": "A `wait` is an ordinary action on the path, not a property of its neighbours. This one holds the contact for two days before the follow-up.\n\n`startAfter.type` accepts `seconds`, `minutes`, `hour`, `hours` or `days`, and `when` is `before`, `after` or `now`. Business-hours behaviour goes in `settings.window` at the root — not on the wait step.",
                  "value": {
                    "version": 1,
                    "name": "Follow up after two days",
                    "source": {
                      "system": "api"
                    },
                    "triggers": [
                      {
                        "type": "contact_created"
                      }
                    ],
                    "actions": [
                      {
                        "id": "wait-one-day",
                        "type": "wait",
                        "name": "Wait 2 days",
                        "attributes": {
                          "type": "time",
                          "startAfter": {
                            "when": "after",
                            "type": "days",
                            "value": 2
                          }
                        }
                      },
                      {
                        "id": "send-welcome-sms",
                        "type": "sms",
                        "name": "Follow-up SMS",
                        "attributes": {
                          "body": "Still interested?"
                        }
                      }
                    ],
                    "edges": [
                      {
                        "from": "wait-one-day",
                        "to": "send-welcome-sms"
                      }
                    ],
                    "settings": {
                      "timezone": "account",
                      "window": {
                        "condition": "when",
                        "start": "09:00",
                        "end": "17:00",
                        "days": [
                          1,
                          2,
                          3,
                          4,
                          5
                        ]
                      }
                    }
                  }
                },
                "webhook": {
                  "summary": "4. Webhook — call an external system",
                  "description": "`method` and `url` are required; `headers` and `customData` are arrays of `{key, value}` pairs rather than plain objects. `customData` becomes the outgoing payload, and merge tags in a value are resolved per contact at send time.",
                  "value": {
                    "version": 1,
                    "name": "Notify external CRM",
                    "source": {
                      "system": "api"
                    },
                    "triggers": [
                      {
                        "type": "contact_created"
                      }
                    ],
                    "actions": [
                      {
                        "id": "send-welcome-sms",
                        "type": "webhook",
                        "name": "POST to CRM",
                        "attributes": {
                          "method": "POST",
                          "url": "https://example.com/hooks/new-contact",
                          "headers": [
                            {
                              "key": "X-Api-Key",
                              "value": "your-api-key"
                            }
                          ],
                          "customData": [
                            {
                              "key": "email",
                              "value": "{{contact.email}}"
                            },
                            {
                              "key": "source",
                              "value": "highlevel"
                            }
                          ]
                        }
                      }
                    ],
                    "edges": []
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Validation result",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ValidateWorkflowDefinitionResponseDto"
                }
              }
            }
          },
          "400": {
            "description": "Bad Request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/BadRequestDTO"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/UnauthorizedDTO"
                }
              }
            }
          },
          "403": {
            "description": "The sub-account is not enrolled in the public workflow API. Not a scope failure.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WorkflowApiNotEnabledDto"
                }
              }
            }
          },
          "422": {
            "description": "Unprocessable Entity",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WorkflowDefinitionUnprocessableDto"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded. Every public token has a daily quota and a short burst limit, and this API adds a per-sub-account budget — reads and writes are counted separately, and create, update and validate all count as writes. Back off for `X-RateLimit-Interval-Milliseconds` and retry.",
            "headers": {
              "X-RateLimit-Max": {
                "description": "Requests allowed in the current burst window",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Requests left in the current burst window",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Interval-Milliseconds": {
                "description": "Length of the burst window",
                "schema": {
                  "type": "integer",
                  "example": 10000
                }
              },
              "X-RateLimit-Limit-Daily": {
                "description": "Daily request quota for the token",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Daily-Remaining": {
                "description": "Requests left in the daily quota",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Daily-Reset": {
                "description": "When the daily quota resets",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WorkflowRateLimitedDto"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearer": [
              "workflows.write"
            ]
          }
        ]
      }
    }
  },
  "components": {
    "securitySchemes": {
      "bearer": {
        "scheme": "bearer",
        "bearerFormat": "JWT",
        "name": "Authorization",
        "in": "header",
        "description": "Use the Access Token generated with user type as Sub-Account (OR) Private Integration Token of Sub-Account.",
        "type": "http"
      },
      "Location-Access": {
        "scheme": "bearer",
        "bearerFormat": "JWT",
        "name": "Authorization",
        "in": "header",
        "description": "Use the Access Token generated with user type as Sub-Account (OR) Private Integration Token of Sub-Account.",
        "type": "http"
      },
      "Location-Access-Only": {
        "scheme": "bearer",
        "bearerFormat": "JWT",
        "name": "Authorization",
        "in": "header",
        "description": "Use the Access Token generated with user type as Sub-Account.",
        "type": "http"
      },
      "Agency-Access": {
        "scheme": "bearer",
        "bearerFormat": "JWT",
        "name": "Authorization",
        "in": "header",
        "description": "Use the Access Token generated with user type as Agency (OR) Private Integration Token of Agency.",
        "type": "http"
      },
      "Agency-Access-Only": {
        "scheme": "bearer",
        "bearerFormat": "JWT",
        "name": "Authorization",
        "in": "header",
        "description": "Use the Access Token generated with user type as Agency.",
        "type": "http"
      }
    },
    "schemas": {
      "ValidateWorkflowDefinitionDto": {
        "type": "object",
        "properties": {
          "name": {
            "type": "string",
            "description": "Workflow name",
            "example": "Welcome series"
          },
          "source": {
            "description": "Where this definition came from",
            "example": {
              "system": "api"
            },
            "allOf": [
              {
                "type": "object",
                "properties": {
                  "system": {
                    "type": "string",
                    "description": "Identifier for the system authoring this definition",
                    "example": "my-app"
                  },
                  "workflowId": {
                    "type": "string",
                    "description": "Your own identifier for the workflow, echoed back for correlation",
                    "example": "wf-42"
                  }
                },
                "required": [
                  "system"
                ],
                "title": "WorkflowDefinitionSourceDto"
              }
            ]
          },
          "triggers": {
            "description": "Triggers that enrol contacts. May be empty — a workflow with no trigger can still be entered by another workflow or by the API — but the key must be present.",
            "example": [
              {
                "type": "contact_created",
                "name": "New contact"
              }
            ],
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "type": {
                  "type": "string",
                  "description": "Trigger type key",
                  "example": "contact_created"
                },
                "name": {
                  "type": "string",
                  "description": "Display name shown on the canvas",
                  "example": "New contact"
                },
                "conditions": {
                  "type": "array",
                  "description": "Trigger filters. Shape depends on `type`; use Validate Workflow Definition to check it.",
                  "items": {
                    "type": "object",
                    "additionalProperties": true
                  },
                  "example": []
                },
                "scheduleConfig": {
                  "description": "Schedule for scheduler-type triggers. Absent on every other trigger type. Get Workflow Definition returns it in this shape and Update Workflow Definition accepts it back.",
                  "example": {
                    "stopAt": 1767225600000,
                    "skipWeekends": true
                  },
                  "allOf": [
                    {
                      "type": "object",
                      "properties": {
                        "stopAt": {
                          "type": "number",
                          "description": "When the schedule stops firing, as epoch milliseconds (UTC). Omit for no end date.",
                          "example": 1767225600000
                        },
                        "skipWeekends": {
                          "type": "boolean",
                          "description": "Skip runs that would fall on a Saturday or Sunday.",
                          "example": true
                        }
                      },
                      "title": "WorkflowDefinitionScheduleConfigDto"
                    }
                  ]
                },
                "id": {
                  "type": "string",
                  "description": "Trigger ID. Assigned when omitted. Get Workflow Definition returns it; sending it back keeps the trigger stable across updates.",
                  "example": "7c1f0d2e-3b4a-4c5d-8e9f-0a1b2c3d4e5f",
                  "maxLength": 200
                },
                "position": {
                  "description": "Canvas position, as the builder stores it. Echoed by Get Workflow Definition; optional on write.",
                  "example": {
                    "x": 120,
                    "y": 240
                  },
                  "allOf": [
                    {
                      "type": "object",
                      "properties": {
                        "x": {
                          "type": "number",
                          "description": "Horizontal canvas position",
                          "example": 120
                        },
                        "y": {
                          "type": "number",
                          "description": "Vertical canvas position",
                          "example": 240
                        }
                      },
                      "required": [
                        "x",
                        "y"
                      ],
                      "title": "WorkflowDefinitionPositionDto"
                    }
                  ]
                },
                "testResponse": {
                  "type": "object",
                  "description": "Sample event captured by the builder for testing the trigger. Passed through verbatim.",
                  "additionalProperties": true,
                  "example": {
                    "contact": {
                      "id": "c_01HZX3K9P2Q",
                      "email": "jane@example.com"
                    }
                  }
                },
                "metadata": {
                  "type": "object",
                  "description": "Importer bookkeeping. Passed through verbatim.",
                  "additionalProperties": true,
                  "example": {
                    "unmapped": false
                  }
                },
                "workflowsTriggerType": {
                  "type": "string",
                  "description": "Marketplace (IATF) triggers only: the trigger class the app registered. Echoed by Get Workflow Definition; send it back unchanged.",
                  "example": "INTERNAL"
                },
                "integrationAccountId": {
                  "type": "string",
                  "description": "Marketplace (IATF) triggers only: the connected account the trigger listens on.",
                  "example": "acct_01HZX3K9P2Q"
                },
                "masterType": {
                  "type": "string",
                  "description": "Trigger family, as the builder stores it.",
                  "example": "internal"
                },
                "targetActionId": {
                  "type": "string",
                  "description": "For triggers bound to one step (a reply trigger, for example), the ID of that step.",
                  "example": "welcome-sms"
                }
              },
              "required": [
                "type"
              ],
              "title": "WorkflowDefinitionTriggerDto"
            }
          },
          "triggerRelation": {
            "type": "string",
            "description": "How multiple triggers combine. Echoed by Get Workflow Definition; ignored on write — the system always stores `or`.",
            "enum": [
              "and",
              "or"
            ],
            "example": "or"
          },
          "actions": {
            "description": "Steps. May be an empty array, but the key must be present.",
            "example": [
              {
                "id": "a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d",
                "type": "sms",
                "attributes": {
                  "body": "Hi there"
                }
              }
            ],
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "id": {
                  "type": "string",
                  "description": "Step ID. Any non-empty string, unique within the definition — a readable id like `welcome-sms` is fine, and `edges` reference it by that name. It does not have to be a UUID; ids that are not get converted to one when the workflow is saved, so a definition read back will carry the converted ids rather than the ones sent.\n\n**Reuse ids across updates.** A step keeps contacts already mid-workflow on their current step only while it keeps its id. Sending back the ids from Get Workflow Definition is the reliable way to do that — a readable id also converts to the same value every time for a given workflow, so re-sending an authored definition is safe too.",
                  "example": "welcome-sms",
                  "maxLength": 200
                },
                "type": {
                  "type": "string",
                  "description": "Step type key",
                  "example": "add_contact_tag"
                },
                "name": {
                  "type": "string",
                  "description": "Display name shown on the canvas",
                  "example": "Tag as lead"
                },
                "attributes": {
                  "type": "object",
                  "description": "Step configuration. The shape depends on `type` — fetch it from Get Step Types and check it with Validate Workflow Definition.",
                  "additionalProperties": true,
                  "example": {
                    "tags": [
                      "new-lead"
                    ]
                  }
                },
                "position": {
                  "description": "Canvas position, as the builder stores it. Echoed by Get Workflow Definition; optional on write.",
                  "example": {
                    "x": 120,
                    "y": 240
                  },
                  "allOf": [
                    {
                      "type": "object",
                      "properties": {
                        "x": {
                          "type": "number",
                          "description": "Horizontal canvas position",
                          "example": 120
                        },
                        "y": {
                          "type": "number",
                          "description": "Vertical canvas position",
                          "example": 240
                        }
                      },
                      "required": [
                        "x",
                        "y"
                      ],
                      "title": "WorkflowDefinitionPositionDto"
                    }
                  ]
                },
                "metadata": {
                  "type": "object",
                  "description": "Importer bookkeeping. Passed through verbatim.",
                  "additionalProperties": true,
                  "example": {
                    "unmapped": false
                  }
                },
                "workflowsActionType": {
                  "type": "string",
                  "description": "Marketplace (IATF) steps only: the action class the app registered. Echoed by Get Workflow Definition; send it back unchanged.",
                  "example": "INTEGRATION"
                },
                "isMarketplaceAction": {
                  "type": "boolean",
                  "description": "Marketplace (IATF) steps only: marks the step as a marketplace action.",
                  "example": true
                },
                "container": {
                  "type": "string",
                  "description": "ID of the container step this step is nested in, where the builder uses one.",
                  "example": "loop-1"
                }
              },
              "required": [
                "id",
                "type"
              ],
              "title": "WorkflowDefinitionActionDto"
            }
          },
          "edges": {
            "description": "Connections between steps. May be empty, but the key must be present.",
            "example": [
              {
                "from": "a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d",
                "to": "b2c3d4e5-f6a7-4b8c-9d0e-1f2a3b4c5d6e"
              }
            ],
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "from": {
                  "type": "string",
                  "description": "Step ID this edge leaves from",
                  "example": "welcome-sms"
                },
                "to": {
                  "type": "string",
                  "description": "Step ID this edge arrives at",
                  "example": "tag-as-welcomed"
                },
                "branch": {
                  "type": "string",
                  "description": "Branch label, for steps that fan out (e.g. `yes`/`no` on an if/else)",
                  "example": "yes"
                },
                "goto": {
                  "type": "boolean",
                  "description": "Marks this edge as a loop back to an earlier step",
                  "example": false
                }
              },
              "required": [
                "from",
                "to"
              ],
              "title": "WorkflowDefinitionEdgeDto"
            }
          },
          "settings": {
            "description": "Workflow-level settings",
            "example": {
              "timezone": "account"
            },
            "allOf": [
              {
                "type": "object",
                "properties": {
                  "allowReEntry": {
                    "type": "boolean",
                    "description": "Allow a contact to enter the workflow more than once",
                    "example": false
                  },
                  "allowMultipleOpportunities": {
                    "type": "boolean",
                    "description": "Allow multiple opportunities per contact",
                    "example": false
                  },
                  "stopOnResponse": {
                    "type": "boolean",
                    "description": "Stop the workflow when the contact replies",
                    "example": true
                  },
                  "isConversationsMarkAsRead": {
                    "type": "boolean",
                    "description": "Mark conversations as read automatically",
                    "example": false
                  },
                  "timezone": {
                    "type": "string",
                    "description": "Whose timezone drives scheduling",
                    "enum": [
                      "account",
                      "contact"
                    ],
                    "example": "account"
                  },
                  "window": {
                    "description": "Sending window",
                    "example": {
                      "condition": "when",
                      "start": "09:00",
                      "end": "17:00",
                      "days": [
                        1,
                        2,
                        3,
                        4,
                        5
                      ]
                    },
                    "allOf": [
                      {
                        "type": "object",
                        "properties": {
                          "condition": {
                            "type": "string",
                            "description": "How the window is applied",
                            "example": "wait"
                          },
                          "start": {
                            "type": "string",
                            "description": "Window start, 24h HH:mm",
                            "example": "09:00"
                          },
                          "end": {
                            "type": "string",
                            "description": "Window end, 24h HH:mm",
                            "example": "17:00"
                          },
                          "days": {
                            "description": "Days of week the window applies to, 0 = Sunday",
                            "example": [
                              1,
                              2,
                              3,
                              4,
                              5
                            ],
                            "type": "array",
                            "items": {
                              "type": "string"
                            }
                          }
                        },
                        "required": [
                          "condition",
                          "start"
                        ],
                        "title": "WorkflowDefinitionWindowDto"
                      }
                    ]
                  },
                  "senderAddress": {
                    "type": "object",
                    "description": "Default sender details, passed through verbatim. Recognised keys are `from_name`, `from_email` and `from_number`. Declared as a free-form object rather than a typed schema because those keys are snake_case on the wire — the workflow format uses them verbatim, and a typed schema would fail the public-spec camelCase rule while renaming them would break round-tripping.",
                    "additionalProperties": true,
                    "example": {
                      "from_name": "Acme Dental",
                      "from_email": "hello@acme.example"
                    }
                  }
                },
                "title": "WorkflowDefinitionSettingsDto"
              }
            ]
          },
          "folderName": {
            "type": "string",
            "description": "Single-segment folder to place the workflow in. Mutually exclusive with `folderPath`.",
            "example": "Sales"
          },
          "folderPath": {
            "description": "Folder path, outermost first, max 10 levels. Mutually exclusive with `folderName`.",
            "example": [
              "Sales",
              "Q3"
            ],
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "createFolderIfMissing": {
            "type": "boolean",
            "description": "Create the folder if it does not exist. Requires `folderName` or `folderPath`.",
            "example": true
          },
          "version": {
            "type": "number",
            "description": "The workflow's version counter, owned by the system — not a format version. Get Workflow Definition fills it in; leave it untouched and send it back on update, and a concurrent edit in the builder can never be silently overwritten. A stale value is rejected with 422.\n\nTreat it as opaque: echo it, never compute from it. It does not always advance by one (delete and restore each add 0.1, and a later save rounds up), so it is not always an integer.\n\nOptional here: a create-shaped definition has no counter yet.",
            "example": 3
          },
          "workflowId": {
            "type": "string",
            "description": "Validate as an update of this workflow rather than as a create. 404 if it does not exist.",
            "example": "b1b0de82-3efb-4d63-83f6-13c50aef208c"
          }
        },
        "required": [
          "name",
          "source",
          "triggers",
          "actions",
          "edges"
        ],
        "title": "ValidateWorkflowDefinitionDto"
      },
      "ValidateWorkflowDefinitionResponseDto": {
        "type": "object",
        "properties": {
          "valid": {
            "type": "boolean",
            "description": "Whether this exact body would save right now. `false` is still a 200.",
            "example": false
          },
          "errors": {
            "description": "Blocking findings",
            "example": [
              {
                "code": "STEP_VALIDATION_FAILED",
                "message": "Action is missing a required field.",
                "stage": "attributes"
              }
            ],
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "code": {
                  "type": "string",
                  "description": "Stable error code you can branch on",
                  "example": "MISSING_REQUIRED_FIELDS"
                },
                "message": {
                  "type": "string",
                  "description": "Human-readable explanation",
                  "example": "Body is required"
                },
                "path": {
                  "type": "string",
                  "description": "Path within the definition",
                  "example": "definition.actions[2].attributes.body"
                },
                "stage": {
                  "type": "string",
                  "description": "Which validation phase produced this",
                  "example": "schema"
                },
                "context": {
                  "type": "object",
                  "description": "Which step or asset the finding relates to",
                  "additionalProperties": true,
                  "example": {
                    "stepId": "9f2c1e40-…",
                    "stepName": "SMS",
                    "stepType": "sms"
                  }
                }
              },
              "title": "WorkflowDefinitionErrorDto"
            }
          },
          "warnings": {
            "description": "Non-blocking findings",
            "example": [
              {
                "path": "actions[4]",
                "message": "Step has no incoming edges and may be unreachable."
              }
            ],
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "path": {
                  "type": "string",
                  "description": "Path within the definition",
                  "example": "actions[4]"
                },
                "message": {
                  "type": "string",
                  "description": "What to be aware of",
                  "example": "Action 'orphan-step' has no incoming edges and may be unreachable."
                },
                "type": {
                  "type": "string",
                  "description": "Step type the warning relates to",
                  "example": "sms"
                }
              },
              "title": "WorkflowDefinitionWarningDto"
            }
          }
        },
        "required": [
          "valid",
          "errors",
          "warnings"
        ],
        "title": "ValidateWorkflowDefinitionResponseDto"
      },
      "BadRequestDTO": {
        "type": "object",
        "properties": {
          "statusCode": {
            "type": "number",
            "example": 400
          },
          "message": {
            "type": "string",
            "example": "Bad Request"
          }
        },
        "title": "BadRequestDTO"
      },
      "UnauthorizedDTO": {
        "type": "object",
        "properties": {
          "statusCode": {
            "type": "number",
            "example": 401
          },
          "message": {
            "type": "string",
            "example": "Invalid token: access token is invalid"
          },
          "error": {
            "type": "string",
            "example": "Unauthorized"
          }
        },
        "title": "UnauthorizedDTO"
      },
      "WorkflowApiNotEnabledDto": {
        "type": "object",
        "properties": {
          "statusCode": {
            "type": "number",
            "description": "HTTP status code",
            "example": 403
          },
          "error": {
            "type": "string",
            "description": "HTTP error name",
            "example": "Forbidden"
          },
          "code": {
            "type": "string",
            "description": "Stable code to branch on. Only ever this value for an enrolment refusal.",
            "example": "WORKFLOW_API_NOT_ENABLED_FOR_LOCATION"
          },
          "message": {
            "type": "string",
            "description": "Why the request was refused, and what to do about it",
            "example": "The workflow API is not enabled for location ve9EPM428h8vShlRW1KT. This is not a token or scope problem — the API is currently available to selected sub-accounts only, and this one is not among them. Contact HighLevel support to request access; no change to your integration or its scopes is needed."
          }
        },
        "title": "WorkflowApiNotEnabledDto"
      },
      "WorkflowDefinitionUnprocessableDto": {
        "type": "object",
        "properties": {
          "statusCode": {
            "type": "number",
            "description": "HTTP status code",
            "example": 422
          },
          "message": {
            "description": "Validation errors describing why the request could not be processed",
            "example": [
              "Unprocessable Entity"
            ],
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "error": {
            "type": "string",
            "description": "HTTP error name",
            "example": "Unprocessable Entity"
          },
          "errors": {
            "description": "Per-finding detail. This is what tells you which step is wrong.",
            "example": [
              {
                "code": "STEP_VALIDATION_FAILED",
                "message": "Action is missing a required field.",
                "stage": "attributes"
              }
            ],
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "code": {
                  "type": "string",
                  "description": "Stable error code you can branch on",
                  "example": "MISSING_REQUIRED_FIELDS"
                },
                "message": {
                  "type": "string",
                  "description": "Human-readable explanation",
                  "example": "Body is required"
                },
                "path": {
                  "type": "string",
                  "description": "Path within the definition",
                  "example": "definition.actions[2].attributes.body"
                },
                "stage": {
                  "type": "string",
                  "description": "Which validation phase produced this",
                  "example": "schema"
                },
                "context": {
                  "type": "object",
                  "description": "Which step or asset the finding relates to",
                  "additionalProperties": true,
                  "example": {
                    "stepId": "9f2c1e40-…",
                    "stepName": "SMS",
                    "stepType": "sms"
                  }
                }
              },
              "title": "WorkflowDefinitionErrorDto"
            }
          }
        },
        "title": "WorkflowDefinitionUnprocessableDto"
      },
      "WorkflowRateLimitedDto": {
        "type": "object",
        "properties": {
          "statusCode": {
            "type": "number",
            "description": "HTTP status code",
            "example": 429
          },
          "message": {
            "type": "string",
            "description": "Why the request was refused",
            "example": "Too Many Requests"
          }
        },
        "title": "WorkflowRateLimitedDto"
      }
    }
  }
}
```

## HighLevel SDK Examples

**Node.js** (`@gohighlevel/api-client`)

```javascript
import HighLevel from '@gohighlevel/api-client';

const ghl = new HighLevel({
  privateIntegrationToken: '<PRIVATE_INTEGRATION_TOKEN>'
});

const body = {
  "version": 1,
  "name": "Welcome new contacts",
  "source": {
    "system": "api"
  },
  "triggers": [
    {
      "type": "contact_created",
      "name": "New contact"
    }
  ],
  "actions": [
    {
      "id": "send-welcome-sms",
      "type": "sms",
      "name": "Send welcome SMS",
      "attributes": {
        "body": "Hi {{contact.first_name}}, thanks for getting in touch!"
      }
    },
    {
      "id": "tag-as-welcomed",
      "type": "add_contact_tag",
      "name": "Tag as welcomed",
      "attributes": {
        "tags": [
          "your-existing-tag"
        ]
      }
    }
  ],
  "edges": [
    {
      "from": "send-welcome-sms",
      "to": "tag-as-welcomed"
    }
  ]
};

const response = await ghl.workflows.validateWorkflowDefinition({ locationId: '<LOCATIONID>' }, body);
console.log(response);
```

**Python** (`gohighlevel-api-client`)

```python
from highlevel import HighLevel

client = HighLevel(
    private_integration_token='<PRIVATE_INTEGRATION_TOKEN>'
)

body = {
    "version": 1,
    "name": "Welcome new contacts",
    "source": {
        "system": "api"
    },
    "triggers": [
        {
            "type": "contact_created",
            "name": "New contact"
        }
    ],
    "actions": [
        {
            "id": "send-welcome-sms",
            "type": "sms",
            "name": "Send welcome SMS",
            "attributes": {
                "body": "Hi {{contact.first_name}}, thanks for getting in touch!"
            }
        },
        {
            "id": "tag-as-welcomed",
            "type": "add_contact_tag",
            "name": "Tag as welcomed",
            "attributes": {
                "tags": [
                    "your-existing-tag"
                ]
            }
        }
    ],
    "edges": [
        {
            "from": "send-welcome-sms",
            "to": "tag-as-welcomed"
        }
    ]
}

response = await client.workflows.validate_workflow_definition(locationId='<LOCATIONID>', body=body)
print(response)
```

**PHP** (`gohighlevel/api-client`)

```php
<?php
require_once 'vendor/autoload.php';

use HighLevel\HighLevel;
use HighLevel\HighLevelConfig;

$config = new HighLevelConfig([
    'privateIntegrationToken' => '<PRIVATE_INTEGRATION_TOKEN>'
]);
$ghl = new HighLevel($config);

$body = [
    'version' => 1,
    'name' => 'Welcome new contacts',
    'source' => [
        'system' => 'api'
    ],
    'triggers' => [
        [
            'type' => 'contact_created',
            'name' => 'New contact'
        ]
    ],
    'actions' => [
        [
            'id' => 'send-welcome-sms',
            'type' => 'sms',
            'name' => 'Send welcome SMS',
            'attributes' => [
                'body' => 'Hi {{contact.first_name}}, thanks for getting in touch!'
            ]
        ],
        [
            'id' => 'tag-as-welcomed',
            'type' => 'add_contact_tag',
            'name' => 'Tag as welcomed',
            'attributes' => [
                'tags' => [
                    'your-existing-tag'
                ]
            ]
        ]
    ],
    'edges' => [
        [
            'from' => 'send-welcome-sms',
            'to' => 'tag-as-welcomed'
        ]
    ]
];

$response = $ghl->workflows->validateWorkflowDefinition(['locationId' => '<LOCATIONID>'], $body);
echo json_encode($response, JSON_PRETTY_PRINT);
```

## Code Examples

**cURL**

```bash
curl -L -X POST 'https://services.leadconnectorhq.com/workflows/definition/validate?locationId=<LOCATIONID>' \
  -H 'Authorization: Bearer <TOKEN>' \
  -H 'Version: v4' \
  -H 'Content-Type: application/json' \
  --data-raw '{
  "version": 1,
  "name": "Welcome new contacts",
  "source": {
    "system": "api"
  },
  "triggers": [
    {
      "type": "contact_created",
      "name": "New contact"
    }
  ],
  "actions": [
    {
      "id": "send-welcome-sms",
      "type": "sms",
      "name": "Send welcome SMS",
      "attributes": {
        "body": "Hi {{contact.first_name}}, thanks for getting in touch!"
      }
    },
    {
      "id": "tag-as-welcomed",
      "type": "add_contact_tag",
      "name": "Tag as welcomed",
      "attributes": {
        "tags": [
          "your-existing-tag"
        ]
      }
    }
  ],
  "edges": [
    {
      "from": "send-welcome-sms",
      "to": "tag-as-welcomed"
    }
  ]
}'
```

**Node.js**

```javascript
const options = {
  method: 'POST',
  headers: {
    Authorization: 'Bearer <TOKEN>',
    Version: 'v4',
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({
      "version": 1,
      "name": "Welcome new contacts",
      "source": {
        "system": "api"
      },
      "triggers": [
        {
          "type": "contact_created",
          "name": "New contact"
        }
      ],
      "actions": [
        {
          "id": "send-welcome-sms",
          "type": "sms",
          "name": "Send welcome SMS",
          "attributes": {
            "body": "Hi {{contact.first_name}}, thanks for getting in touch!"
          }
        },
        {
          "id": "tag-as-welcomed",
          "type": "add_contact_tag",
          "name": "Tag as welcomed",
          "attributes": {
            "tags": [
              "your-existing-tag"
            ]
          }
        }
      ],
      "edges": [
        {
          "from": "send-welcome-sms",
          "to": "tag-as-welcomed"
        }
      ]
  })
};

try {
  const response = await fetch('https://services.leadconnectorhq.com/workflows/definition/validate?locationId=<LOCATIONID>', options);
  const data = await response.json();
  console.log(data);
} catch (error) {
  console.error(error);
}
```

**Python**

```python
import requests

url = "https://services.leadconnectorhq.com/workflows/definition/validate"

headers = {
    "Authorization": "Bearer <TOKEN>",
    "Version": "v4"
}

params = {
    "locationId": "<LOCATIONID>"
}

payload = {
    "version": 1,
    "name": "Welcome new contacts",
    "source": {
        "system": "api"
    },
    "triggers": [
        {
            "type": "contact_created",
            "name": "New contact"
        }
    ],
    "actions": [
        {
            "id": "send-welcome-sms",
            "type": "sms",
            "name": "Send welcome SMS",
            "attributes": {
                "body": "Hi {{contact.first_name}}, thanks for getting in touch!"
            }
        },
        {
            "id": "tag-as-welcomed",
            "type": "add_contact_tag",
            "name": "Tag as welcomed",
            "attributes": {
                "tags": [
                    "your-existing-tag"
                ]
            }
        }
    ],
    "edges": [
        {
            "from": "send-welcome-sms",
            "to": "tag-as-welcomed"
        }
    ]
}

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

**PHP**

```php
<?php
$curl = curl_init();

curl_setopt_array($curl, [
  CURLOPT_URL => 'https://services.leadconnectorhq.com/workflows/definition/validate?locationId=<LOCATIONID>',
  CURLOPT_RETURNTRANSFER => true,
  CURLOPT_CUSTOMREQUEST => 'POST',
  CURLOPT_HTTPHEADER => [
    'Authorization: Bearer <TOKEN>',
    'Version: v4',
    'Content-Type: application/json',
  ],
  CURLOPT_POSTFIELDS => '{
  "version": 1,
  "name": "Welcome new contacts",
  "source": {
    "system": "api"
  },
  "triggers": [
    {
      "type": "contact_created",
      "name": "New contact"
    }
  ],
  "actions": [
    {
      "id": "send-welcome-sms",
      "type": "sms",
      "name": "Send welcome SMS",
      "attributes": {
        "body": "Hi {{contact.first_name}}, thanks for getting in touch!"
      }
    },
    {
      "id": "tag-as-welcomed",
      "type": "add_contact_tag",
      "name": "Tag as welcomed",
      "attributes": {
        "tags": [
          "your-existing-tag"
        ]
      }
    }
  ],
  "edges": [
    {
      "from": "send-welcome-sms",
      "to": "tag-as-welcomed"
    }
  ]
}',
]);

$response = curl_exec($curl);
curl_close($curl);
echo $response;
```

**Java**

```java
HttpResponse<String> response = Unirest
  .post("https://services.leadconnectorhq.com/workflows/definition/validate?locationId=<LOCATIONID>")
  .header("Authorization", "Bearer <TOKEN>")
  .header("Version", "v4")
  .header("Content-Type", "application/json")
  .body("{\"version\":1,\"name\":\"Welcome new contacts\",\"source\":{\"system\":\"api\"},\"triggers\":[{\"type\":\"contact_created\",\"name\":\"New contact\"}],\"actions\":[{\"id\":\"send-welcome-sms\",\"type\":\"sms\",\"name\":\"Send welcome SMS\",\"attributes\":{\"body\":\"Hi {{contact.first_name}}, thanks for getting in touch!\"}},{\"id\":\"tag-as-welcomed\",\"type\":\"add_contact_tag\",\"name\":\"Tag as welcomed\",\"attributes\":{\"tags\":[\"your-existing-tag\"]}}],\"edges\":[{\"from\":\"send-welcome-sms\",\"to\":\"tag-as-welcomed\"}]}")
  .asString();
```

**Go**

```go
package main

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

func main() {
  url := "https://services.leadconnectorhq.com/workflows/definition/validate?locationId=<LOCATIONID>"
  payload := strings.NewReader(`{
  "version": 1,
  "name": "Welcome new contacts",
  "source": {
    "system": "api"
  },
  "triggers": [
    {
      "type": "contact_created",
      "name": "New contact"
    }
  ],
  "actions": [
    {
      "id": "send-welcome-sms",
      "type": "sms",
      "name": "Send welcome SMS",
      "attributes": {
        "body": "Hi {{contact.first_name}}, thanks for getting in touch!"
      }
    },
    {
      "id": "tag-as-welcomed",
      "type": "add_contact_tag",
      "name": "Tag as welcomed",
      "attributes": {
        "tags": [
          "your-existing-tag"
        ]
      }
    }
  ],
  "edges": [
    {
      "from": "send-welcome-sms",
      "to": "tag-as-welcomed"
    }
  ]
}`)
  req, _ := http.NewRequest("POST", url, payload)
  req.Header.Add("Authorization", "Bearer <TOKEN>")
  req.Header.Add("Version", "v4")

  res, _ := http.DefaultClient.Do(req)
  defer res.Body.Close()
  body, _ := io.ReadAll(res.Body)
  fmt.Println(string(body))
}
```

**Ruby**

```ruby
require 'uri'
require 'net/http'

url = URI("https://services.leadconnectorhq.com/workflows/definition/validate?locationId=<LOCATIONID>")

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

request = Net::HTTP::Post.new(url)
request["Authorization"] = "Bearer <TOKEN>"
request["Version"] = "v4"
request["Content-Type"] = "application/json"
request.body = '{
  "version": 1,
  "name": "Welcome new contacts",
  "source": {
    "system": "api"
  },
  "triggers": [
    {
      "type": "contact_created",
      "name": "New contact"
    }
  ],
  "actions": [
    {
      "id": "send-welcome-sms",
      "type": "sms",
      "name": "Send welcome SMS",
      "attributes": {
        "body": "Hi {{contact.first_name}}, thanks for getting in touch!"
      }
    },
    {
      "id": "tag-as-welcomed",
      "type": "add_contact_tag",
      "name": "Tag as welcomed",
      "attributes": {
        "tags": [
          "your-existing-tag"
        ]
      }
    }
  ],
  "edges": [
    {
      "from": "send-welcome-sms",
      "to": "tag-as-welcomed"
    }
  ]
}'

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

**PowerShell**

```powershell
$headers = @{
  "Authorization" = "Bearer <TOKEN>"
  "Version" = "v4"
}

$body = '{
  "version": 1,
  "name": "Welcome new contacts",
  "source": {
    "system": "api"
  },
  "triggers": [
    {
      "type": "contact_created",
      "name": "New contact"
    }
  ],
  "actions": [
    {
      "id": "send-welcome-sms",
      "type": "sms",
      "name": "Send welcome SMS",
      "attributes": {
        "body": "Hi {{contact.first_name}}, thanks for getting in touch!"
      }
    },
    {
      "id": "tag-as-welcomed",
      "type": "add_contact_tag",
      "name": "Tag as welcomed",
      "attributes": {
        "tags": [
          "your-existing-tag"
        ]
      }
    }
  ],
  "edges": [
    {
      "from": "send-welcome-sms",
      "to": "tag-as-welcomed"
    }
  ]
}'

$response = Invoke-RestMethod -Uri 'https://services.leadconnectorhq.com/workflows/definition/validate?locationId=<LOCATIONID>' -Method 'POST' -Headers $headers -Body $body -ContentType 'application/json'
$response | ConvertTo-Json
```
