BPMN Validator API
Send a BPMN 2.0 file as XML and get a JSON report back: errors, warnings and information, each with the rule that raised it and its position in the file. The API is free and needs no account and no key. It runs the same engine as the online BPMN validator of LucidFlow. The XML is checked in memory and only the report is returned: the API code does not save the file.
One command
Replace process.bpmn with your file:
curl -X POST https://lucidflow.ai/api/v1/bpmn/validate \
-H "Content-Type: application/xml" \
--data-binary @process.bpmnRequest
One endpoint, one method. The API takes the content of the file, not a link to it.
| Item | Value |
|---|---|
| Method and URL | POST https://lucidflow.ai/api/v1/bpmn/validate |
| Authentication | None. No account, no key. |
| Raw body | The BPMN XML as is. Use any Content-Type that does not contain "json", for example application/xml. |
| JSON body | {"xml": "<the BPMN XML as a string>"} with Content-Type: application/json. |
| Browsers | The API sends no CORS headers. Call it from a server, a script or an agent, not from a web page on another domain. |
A small file you can save as process.bpmn to try the command above. Its process has no end event, so the report below shows a warning:
<?xml version="1.0" encoding="UTF-8"?>
<definitions xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL"
id="defs" targetNamespace="http://example.com/bpmn">
<process id="order" name="Order handling" isExecutable="true">
<startEvent id="start"/>
<task id="review" name="Review order"/>
<sequenceFlow id="f1" sourceRef="start" targetRef="review"/>
</process>
</definitions># Same call with a JSON body
curl -X POST https://lucidflow.ai/api/v1/bpmn/validate \
-H "Content-Type: application/json" \
-d '{"xml": "<definitions ...>...</definitions>}'Response
A valid request answers 200 with a JSON report. A file that is not well-formed XML, or not BPMN, is not an HTTP error: the answer is still 200, with valid set to false and the problems listed in errors.
Abridged report for the sample file above: only the first warning is listed, and the second warning and the information items are left out. The lists and the counts depend on your file.
{
"version": 1,
"generator": { "name": "...", "version": "...", "url": "..." },
"valid": true,
"summary": { "errors": 0, "warnings": 2, "info": 3 },
"stats": {
"processCount": 1,
"taskCount": 1,
"gatewayCount": 0,
"eventCount": 1,
"flowCount": 1,
"laneCount": 0,
"taskBreakdown": { "topLevel": 1, "nested": 0 }
},
"errors": [],
"warnings": [
{
"severity": "warning",
"rule": "noEndEvent",
"message": "Process \"order\" has no end event",
"location": { "line": 4, "column": 12 }
}
],
"info": [],
"lucidflow": {
"message": "...",
"signup_url": "...",
"tool_url": "..."
}
}| Field | Meaning |
|---|---|
| version | Version of the report format. Currently 1. |
| generator | Name, version and address of the validator. |
| valid | true when the file has no error. Warnings and information do not make a file invalid. |
| summary | Number of errors, warnings and information items. |
| stats | Counts found in the file: processes, tasks, gateways, events, flows, lanes. taskBreakdown splits the tasks into top-level and nested ones. |
| errors, warnings, info | Lists of issues. Each has severity, rule and message, and often location (line and column). Some also have diagnostic, snippet, detail or suggestion. |
| lucidflow | A message and two links to LucidFlow (signup_url and tool_url). Your agent can show them or ignore them. |
Messages and rule names are in English. The rules are listed in the rules reference.
Errors
When the request itself is refused, the body is a JSON object with a code, a message and a link to the rules reference:
| Code | HTTP | Cause |
|---|---|---|
| empty_input | 400 | The body is empty, or the JSON body is {"xml": ""}. |
| invalid_json | 400 | The Content-Type is JSON but the body is not {"xml": "..."} with a string. An empty body with a JSON Content-Type also gives invalid_json. |
| payload_too_large | 413 | The request body is larger than 50 KB. The whole body counts, so the JSON wrapper and its escape characters count too. |
| doctype_not_allowed | 422 | The XML has a DOCTYPE or ENTITY declaration. |
| too_many_elements | 422 | The XML has more than 400 elements. |
| too_deep | 422 | The XML is nested more than 50 levels deep. |
| validation_timeout | 422 | The validation took more than 2 seconds and was stopped. |
| rate_limited | 429 | More than 20 requests in 10 minutes from your IP address. The Retry-After header gives the delay in seconds. |
| daily_budget_exhausted | 429 | The daily capacity of the API is used up. Try again after midnight UTC; Retry-After gives the delay. |
| validation_failed | 500 | The validation could not be completed. |
Example of a refused request:
{
"error": {
"code": "payload_too_large",
"message": "The BPMN XML is larger than 50 KB.",
"docs_url": "https://lucidflow.ai/docs/validator-rules"
}
}Limits
| Limit | Value |
|---|---|
| Size of the XML | 50 KB (51,200 bytes) |
| Elements in the XML | 400 |
| Nesting depth | 50 levels |
| DOCTYPE and ENTITY | Refused |
| Time per validation | 2 seconds, then the validation is stopped |
| Rate per IP address | 20 requests in 10 minutes |
| Total capacity | 20,000 requests per day for all users |
Agents hosted in the cloud often share one IP address, so they can reach the rate limit sooner. The counters are shared by all servers; if the shared store is unavailable, each server counts alone and the limits apply per server. Do not send confidential documents: the XML is processed on LucidFlow servers.
Use it from an AI assistant
The API is a plain HTTP endpoint. Here is how to give it to Claude or ChatGPT.
Claude
Claude Code and any other agent that has a shell: ask it to run the command from the top of this page on your file, and to read valid and summary first.
In an application that uses the Anthropic Messages API, declare a client tool:
{
"name": "validate_bpmn",
"description": "Validate a BPMN 2.0 XML document with the free LucidFlow validator. Returns errors, warnings and info. The XML must be under 50 KB.",
"input_schema": {
"type": "object",
"properties": {
"xml": { "type": "string", "description": "The BPMN 2.0 XML" }
},
"required": ["xml"]
}
}When Claude asks for the tool, run this call in your code and send the text back as the tool result:
async function validateBpmn(xml: string): Promise<string> {
const res = await fetch("https://lucidflow.ai/api/v1/bpmn/validate", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ xml }),
});
if (res.status === 429) {
return "Rate limited. Retry after " + res.headers.get("Retry-After") + " seconds.";
}
return JSON.stringify(await res.json());
}ChatGPT
In a custom GPT, add an action and paste this OpenAPI schema. The GPT then calls the endpoint when you ask it to check a BPMN file. The XML must fit in the message: 50 KB at most.
openapi: 3.1.0
info:
title: LucidFlow BPMN Validator
version: "1"
servers:
- url: https://lucidflow.ai
paths:
/api/v1/bpmn/validate:
post:
operationId: validateBpmn
summary: Validate a BPMN 2.0 XML document (50 KB at most)
requestBody:
required: true
content:
application/json:
schema:
type: object
required: [xml]
properties:
xml:
type: string
description: The BPMN 2.0 XML
responses:
"200":
description: Validation report with errors, warnings and info
content:
application/json:
schema:
type: object
"429":
description: Too many requests, see the Retry-After headerTips for agents
- Read valid and summary first, then fix the errors before the warnings.
- Send the corrected XML again to check it. Stop after a few rounds if the errors do not change.
- On a 429 answer, wait for the Retry-After delay before the next call.
- Do not send files that contain confidential data.
MCP server
The validator is also a remote MCP server. An assistant that supports MCP can call it directly, with no code on your side. The server has one tool, validate_bpmn. It needs no account and no key, applies the same rules and the same limits as the HTTP API. As with the HTTP API, the XML is checked in memory and only the report is returned.
| Item | Value |
|---|---|
| Address | https://lucidflow.ai/api/mcp |
| Transport | Streamable HTTP, stateless. One JSON-RPC message per POST request, JSON replies. GET and DELETE answer 405: there is no stream and no session. |
| Authentication | None. No account, no key. |
| Protocol versions | 2025-06-18, 2025-03-26, 2024-11-05. |
| Tool | validate_bpmn, with one argument: xml. |
| Server card | https://lucidflow.ai/.well-known/mcp/server-card.json |
The validate_bpmn tool
The argument xml is the BPMN 2.0 XML text itself, not a link to it, 50 KB at most. The tool only reads: it does not save the XML and changes nothing. Its result is the JSON report described in the Response section, as text in the first content item.
A refused call is not a protocol error. The HTTP status stays 200: read isError in the result. Its text holds the error object of the Errors section (code, message, docs_url), and rate_limited and daily_budget_exhausted add retry_after_seconds. invalid_arguments is specific to the tool: xml is missing or is not a string. invalid_json does not occur. An unknown tool name or method gets a standard JSON-RPC error.
Try it with curl
List the tool. This does not count against the limits:
curl -X POST https://lucidflow.ai/api/mcp \
-H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'Call it. Replace the XML with your file, written as a JSON string:
curl -X POST https://lucidflow.ai/api/mcp \
-H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"validate_bpmn","arguments":{"xml":"<definitions ...>...</definitions>"}}}'The report comes back as text. This sample is abridged: the real text holds the full report shown in the Response section.
{
"jsonrpc": "2.0",
"id": 2,
"result": {
"content": [
{
"type": "text",
"text": "{\"version\":1,\"valid\":true,\"summary\":{\"errors\":0,\"warnings\":1,\"info\":0}}"
}
]
}
}A refused call, here after too many calls from one IP address:
{
"jsonrpc": "2.0",
"id": 2,
"result": {
"isError": true,
"content": [
{
"type": "text",
"text": "{\"error\":{\"code\":\"rate_limited\",\"message\":\"Too many requests. Try again later.\",\"docs_url\":\"https://lucidflow.ai/docs/validator-rules\",\"retry_after_seconds\":600}}"
}
]
}
}Claude
Claude Code: add the server with one command, then ask Claude to validate a file with the validate_bpmn tool.
claude mcp add --transport http lucidflow-bpmn-validator https://lucidflow.ai/api/mcpTo share the server with a project, put this in a .mcp.json file at the root of the project:
{
"mcpServers": {
"lucidflow-bpmn-validator": {
"type": "http",
"url": "https://lucidflow.ai/api/mcp"
}
}
}Claude apps that support custom connectors (for example claude.ai or Claude Desktop, depending on your plan): open Settings, then Connectors, add a custom connector, paste the address https://lucidflow.ai/api/mcp and leave authentication empty.
ChatGPT
ChatGPT reaches remote MCP servers through connectors, in developer mode on the plans that offer it. Open Settings, then Connectors, then Advanced, turn on developer mode, and create a connector named LucidFlow BPMN validator with the address https://lucidflow.ai/api/mcp and no authentication. Then enable it in a chat and ask ChatGPT to validate your BPMN XML.
Menus and plan availability are set by Anthropic and OpenAI and change often. If a step does not match your screen, follow the help page of your app. The address and the tool are the same everywhere.
Limits of the MCP server
| Limit | Value |
|---|---|
| Calls counted | Only tools/call. initialize, tools/list and ping are free, so a normal session uses one call. |
| Size of the XML | 50 KB, as in the HTTP API. |
| Size of the request | About 108 KB with the JSON envelope. A larger request gets HTTP 413. |
| Rate and capacity | 20 calls in 10 minutes per IP address and 20,000 per day for all users. The counters are shared with the HTTP API. |
| Other checks | Elements, depth, DOCTYPE and time per validation: see Limits above. |
| Not offered | Resources, prompts, streaming and notifications from the server. Tools only. |
The XML is processed on LucidFlow servers. Do not send confidential documents.
To draw, edit and analyze a process in LucidFlow, create a free account:
Go further
The API checks a file. To draw, edit and analyze a process in LucidFlow, create a free account.