# Check a change of one kind and sign a plan for it, without writing anything.

POST https://services.leadconnectorhq.com/ai-builder-api/projects/{projectId}/changes/plan

The caller's AI writes a `change` of one `kind`; the server validates it
against that kind's schema and against what exists now (CRM, modules,
saved customization) and answers what applying it would do. Nothing is
written and no model is called. `planId` is a signed token over
`{projectId, locationId, kind, changeHash}` that expires at `expiresAt`;
it is `null` when `ok` is false. The location is the session's, never
the body's. Kinds: `records`, `modules`, `appearance`, `mobile`,
`assets`, `domain`; any other kind is 400 `unsupported_change_kind`
naming the supported ones. `assets` plans also return `upload`: PUT the
image bytes to `upload.url` with `upload.headers`, then apply (apply is
409 `customizer_image_not_uploaded` until they land, and the plan stays
open). `domain` apply returns the DNS records to add in
`result.dnsRecords`; `GET /custom-domains` reports when it is active.


## Authentication

Send `Authorization: Bearer <token>` using:

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

## Parameters

### Header Parameters

| Name | Type | Required | Description |
|------|------|----------|-------------|
| `Version` | string | Yes | The API version. |

### Path Parameters

| Name | Type | Required | Description |
|------|------|----------|-------------|
| `projectId` | string | Yes | The project's id. |

## OpenAPI Specification

```json
{
  "openapi": "3.0.0",
  "info": {
    "title": "HighLevel API",
    "version": "v3"
  },
  "servers": [
    {
      "url": "https://services.leadconnectorhq.com"
    }
  ],
  "paths": {
    "/ai-builder-api/projects/{projectId}/changes/plan": {
      "post": {
        "summary": "Check a change of one kind and sign a plan for it, without writing anything.",
        "operationId": "checkAChangeOfOneKindAndSignAPlanForItWithoutWritingAnything",
        "description": "The caller's AI writes a `change` of one `kind`; the server validates it\nagainst that kind's schema and against what exists now (CRM, modules,\nsaved customization) and answers what applying it would do. Nothing is\nwritten and no model is called. `planId` is a signed token over\n`{projectId, locationId, kind, changeHash}` that expires at `expiresAt`;\nit is `null` when `ok` is false. The location is the session's, never\nthe body's. Kinds: `records`, `modules`, `appearance`, `mobile`,\n`assets`, `domain`; any other kind is 400 `unsupported_change_kind`\nnaming the supported ones. `assets` plans also return `upload`: PUT the\nimage bytes to `upload.url` with `upload.headers`, then apply (apply is\n409 `customizer_image_not_uploaded` until they land, and the plan stays\nopen). `domain` apply returns the DNS records to add in\n`result.dnsRecords`; `GET /custom-domains` reports when it is active.\n",
        "parameters": [
          {
            "in": "path",
            "name": "projectId",
            "required": true,
            "schema": {
              "type": "string",
              "pattern": "^prj_[A-Za-z0-9_-]+$"
            },
            "description": "The project's id.",
            "example": "prj_5f0c2a3e"
          },
          {
            "name": "Version",
            "in": "header",
            "required": true,
            "description": "The API version.",
            "schema": {
              "type": "string",
              "enum": [
                "v3"
              ]
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/PlanChangeRequest"
              },
              "example": {
                "kind": "modules",
                "change": {
                  "enable": [
                    "courses"
                  ],
                  "disable": []
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The checked plan, or the problems to fix first.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ChangePlan"
                },
                "example": {
                  "planId": "cp1.WyJwcmpfMSIsImxvY18xIiwibW9kdWxlcyIsImFiYyIsMTc2MDAwMDAwMDAwMF0.c2ln",
                  "ok": true,
                  "summary": "1 modules step(s) ready to apply.",
                  "diff": [
                    "Turn on Courses"
                  ],
                  "errors": [],
                  "warnings": [],
                  "expiresAt": "2026-10-07T12:30:00.000Z"
                }
              }
            }
          },
          "400": {
            "description": "Unsupported kind (`unsupported_change_kind`).",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "401": {
            "description": "No valid session."
          },
          "404": {
            "description": "Project not found in this location."
          },
          "422": {
            "description": "Request validation failed.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "503": {
            "description": "The kind's backing service is not configured on this server.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearer": [
              "clientportal-ai.write"
            ]
          }
        ]
      }
    }
  },
  "components": {
    "securitySchemes": {
      "bearer": {
        "type": "http",
        "scheme": "bearer",
        "description": "A sub-account access token: a Marketplace app's OAuth token or a Private Integration Token."
      }
    },
    "schemas": {
      "PlanChangeRequest": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "kind",
          "change"
        ],
        "properties": {
          "kind": {
            "type": "string",
            "pattern": "^[a-z][a-z_]{0,39}$",
            "description": "`records`, `modules`, `appearance`, `mobile`, `assets` or `domain`.",
            "example": "modules"
          },
          "change": {
            "type": "object",
            "additionalProperties": true,
            "description": "The change, in the shape GET /changes/schema gives for `kind`.",
            "example": {
              "disable": [
                "communities"
              ]
            }
          }
        },
        "title": "PlanChangeRequest"
      },
      "ChangePlan": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "planId",
          "ok",
          "summary",
          "diff",
          "errors",
          "warnings",
          "expiresAt"
        ],
        "properties": {
          "planId": {
            "type": [
              "string",
              "null"
            ],
            "description": "Signed, expiring plan id; null when `ok` is false.",
            "example": "cp1.eyJwIjoicHJqXzVmMGMyYTNlIn0.c2lnbmF0dXJl"
          },
          "ok": {
            "type": "boolean",
            "description": "True when the change is valid and can be applied.",
            "example": true
          },
          "summary": {
            "type": "string",
            "description": "The change, in one line.",
            "example": "Turns off Communities"
          },
          "diff": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "What apply would do, one line per step, in order.",
            "example": [
              "Turn off the Communities module"
            ]
          },
          "errors": {
            "type": "array",
            "items": {
              "type": "object",
              "additionalProperties": false,
              "required": [
                "path",
                "message",
                "fix"
              ],
              "properties": {
                "path": {
                  "type": "string",
                  "description": "Where in the change, e.g. `change.disable.0`.",
                  "example": "change.disable.0"
                },
                "message": {
                  "type": "string",
                  "description": "What is wrong.",
                  "example": "Unknown module \"forum\""
                },
                "fix": {
                  "type": "string",
                  "description": "What to send instead.",
                  "example": "Send one of: courses, communities"
                }
              },
              "title": "ChangeError"
            },
            "description": "Why the change cannot be applied, when `ok` is false.",
            "example": [
              {
                "path": "change.disable.0",
                "message": "Unknown module \"forum\"",
                "fix": "Send one of: courses, communities"
              }
            ]
          },
          "warnings": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Things to know before applying; they do not block it.",
            "example": [
              "Members lose access to their community posts"
            ]
          },
          "expiresAt": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "When `planId` stops working; null when `ok` is false.",
            "example": "2026-10-15T10:30:00.000Z"
          },
          "upload": {
            "description": "Where to upload the file the change needs, for a change that needs one.",
            "type": "object",
            "additionalProperties": false,
            "required": [
              "method",
              "url",
              "headers",
              "assetId",
              "publicUrl",
              "expiresAt"
            ],
            "properties": {
              "method": {
                "type": "string",
                "enum": [
                  "PUT"
                ],
                "description": "The HTTP method to upload with.",
                "example": "PUT"
              },
              "url": {
                "type": "string",
                "format": "uri",
                "description": "Short-lived signed upload URL.",
                "example": "https://storage.example.com/uploads/asset-1.png?signature=abc123"
              },
              "headers": {
                "type": "object",
                "additionalProperties": {
                  "type": "string"
                },
                "description": "Headers the PUT must carry (the declared content type).",
                "example": {
                  "Content-Type": "image/png"
                }
              },
              "assetId": {
                "type": "string",
                "description": "The asset the upload creates.",
                "example": "ast_7b2e91"
              },
              "publicUrl": {
                "type": "string",
                "format": "uri",
                "description": "Where the image is served once uploaded.",
                "example": "https://assets.example.com/asset-1.png"
              },
              "expiresAt": {
                "type": "string",
                "format": "date-time",
                "description": "When `url` stops accepting the upload.",
                "example": "2026-10-15T10:15:00.000Z"
              }
            },
            "title": "ChangeUpload"
          }
        },
        "title": "ChangePlan"
      },
      "ApiError": {
        "type": "object",
        "required": [
          "type",
          "title",
          "status",
          "code",
          "requestId"
        ],
        "properties": {
          "type": {
            "type": "string",
            "format": "uri",
            "example": "about:blank",
            "description": "A URI identifying the kind of error."
          },
          "title": {
            "type": "string",
            "example": "Request validation failed",
            "description": "Short summary of the kind of error."
          },
          "status": {
            "type": "integer",
            "minimum": 400,
            "maximum": 599,
            "example": 422,
            "description": "The HTTP status code."
          },
          "detail": {
            "type": "string",
            "example": "One or more fields are invalid.",
            "description": "What went wrong with this request."
          },
          "instance": {
            "type": "string",
            "example": "/api/v1/runs",
            "description": "The request path that failed."
          },
          "code": {
            "type": "string",
            "example": "validation_error",
            "description": "Stable, machine-readable error code."
          },
          "requestId": {
            "type": "string",
            "example": "req_01HXQ8Z6J9K2",
            "description": "The request's id, for support."
          },
          "fieldErrors": {
            "type": "array",
            "items": {
              "type": "object",
              "required": [
                "field",
                "code",
                "message"
              ],
              "properties": {
                "field": {
                  "type": "string",
                  "example": "prompt",
                  "description": "The invalid field, as a dotted path."
                },
                "code": {
                  "type": "string",
                  "example": "required",
                  "description": "Machine-readable reason."
                },
                "message": {
                  "type": "string",
                  "example": "Prompt is required.",
                  "description": "Human-readable reason."
                }
              },
              "title": "FieldError"
            },
            "description": "One entry per invalid field, when the request body was invalid.",
            "example": [
              {
                "field": "name",
                "code": "too_long",
                "message": "name must be at most 120 characters"
              }
            ]
          }
        },
        "title": "ApiError"
      }
    }
  }
}
```

## Code Examples

**cURL**

```bash
curl -L -X POST 'https://services.leadconnectorhq.com/ai-builder-api/projects/<projectId>/changes/plan' \
  -H 'Authorization: Bearer <TOKEN>' \
  -H 'Version: v3' \
  -H 'Content-Type: application/json' \
  --data-raw '{
  "kind": "modules",
  "change": {
    "enable": [
      "courses"
    ],
    "disable": []
  }
}'
```

**Node.js**

```javascript
const options = {
  method: 'POST',
  headers: {
    Authorization: 'Bearer <TOKEN>',
    Version: 'v3',
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({
      "kind": "modules",
      "change": {
        "enable": [
          "courses"
        ],
        "disable": []
      }
  })
};

try {
  const response = await fetch('https://services.leadconnectorhq.com/ai-builder-api/projects/<projectId>/changes/plan', options);
  const data = await response.json();
  console.log(data);
} catch (error) {
  console.error(error);
}
```

**Python**

```python
import requests

url = "https://services.leadconnectorhq.com/ai-builder-api/projects/{projectId}/changes/plan"

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

payload = {
    "kind": "modules",
    "change": {
        "enable": [
            "courses"
        ],
        "disable": []
    }
}

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

**PHP**

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

curl_setopt_array($curl, [
  CURLOPT_URL => 'https://services.leadconnectorhq.com/ai-builder-api/projects/<projectId>/changes/plan',
  CURLOPT_RETURNTRANSFER => true,
  CURLOPT_CUSTOMREQUEST => 'POST',
  CURLOPT_HTTPHEADER => [
    'Authorization: Bearer <TOKEN>',
    'Version: v3',
    'Content-Type: application/json',
  ],
  CURLOPT_POSTFIELDS => '{
  "kind": "modules",
  "change": {
    "enable": [
      "courses"
    ],
    "disable": []
  }
}',
]);

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

**Java**

```java
HttpResponse<String> response = Unirest
  .post("https://services.leadconnectorhq.com/ai-builder-api/projects/<projectId>/changes/plan")
  .header("Authorization", "Bearer <TOKEN>")
  .header("Version", "v3")
  .header("Content-Type", "application/json")
  .body("{\"kind\":\"modules\",\"change\":{\"enable\":[\"courses\"],\"disable\":[]}}")
  .asString();
```

**Go**

```go
package main

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

func main() {
  url := "https://services.leadconnectorhq.com/ai-builder-api/projects/<projectId>/changes/plan"
  payload := strings.NewReader(`{
  "kind": "modules",
  "change": {
    "enable": [
      "courses"
    ],
    "disable": []
  }
}`)
  req, _ := http.NewRequest("POST", url, payload)
  req.Header.Add("Authorization", "Bearer <TOKEN>")
  req.Header.Add("Version", "v3")

  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/ai-builder-api/projects/<projectId>/changes/plan")

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

request = Net::HTTP::Post.new(url)
request["Authorization"] = "Bearer <TOKEN>"
request["Version"] = "v3"
request["Content-Type"] = "application/json"
request.body = '{
  "kind": "modules",
  "change": {
    "enable": [
      "courses"
    ],
    "disable": []
  }
}'

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

**PowerShell**

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

$body = '{
  "kind": "modules",
  "change": {
    "enable": [
      "courses"
    ],
    "disable": []
  }
}'

$response = Invoke-RestMethod -Uri 'https://services.leadconnectorhq.com/ai-builder-api/projects/<projectId>/changes/plan' -Method 'POST' -Headers $headers -Body $body -ContentType 'application/json'
$response | ConvertTo-Json
```
