- Missing or duplicate start_document blocks
- Disconnected blocks
- Dangling edges (one endpoint missing)
- Unreachable blocks
- Type mismatches between source/target handles
- Missing required block configuration
- Incomplete authored config such as blank categories, subdocuments, or map keys
- Embedded review gate predicates and unavailable review signals
- Cycles in the directed graph
| Severity | Meaning |
|---|---|
error | Must fix before publish — block runs would fail. |
warning | Should fix; runs may still succeed. |
info | Advisory only. |
is_valid is true when the issue list contains no errors. re_propagate
defaults to true so derived schemas are refreshed before validation.
The SDKs also expose a convenience helper:
from retab import Retab
from retab.types.standards import PreparedRequest
client = Retab()
diagnosis = client._prepared_request(
PreparedRequest(
method="POST",
url="/v1/workflows/wf_abc123/diagnose-graph",
data={"re_propagate": True},
)
)
for issue in diagnosis["issues"]:
print(f"[{issue['severity']}] {issue['code']}: {issue['message']}")
import { Retab } from "@retab/node";
const client = new Retab({ apiKey: process.env.RETAB_API_KEY });
type WorkflowGraphDiagnosis = {
issues: Array<{ severity: string; code: string; message: string }>;
};
const diagnosis = await client.request<WorkflowGraphDiagnosis>({
method: "POST",
path: "/v1/workflows/wf_abc123/diagnose-graph",
body: { re_propagate: true },
});
for (const issue of diagnosis.issues) {
console.log(`[${issue.severity}] ${issue.code}: ${issue.message}`);
}
package main
import (
"context"
"fmt"
"log"
retab "github.com/retab-dev/retab/clients/go"
)
func ptr[T any](v T) *T { return &v }
func main() {
ctx := context.Background()
client, err := retab.NewClient("")
if err != nil {
log.Fatal(err)
}
workflow, err := client.Workflows.Get(ctx, "wf_abc123")
if err != nil {
log.Fatal(err)
}
fmt.Println(workflow.ID)
}
require 'retab'
client = Retab::Client.new(api_key: ENV['RETAB_API_KEY'])
diagnosis = client.workflows.diagnose(workflow_id: 'wf_abc123')
diagnosis.issues.each do |issue|
puts "[#{issue.severity}] #{issue.code}: #{issue.message}"
end
use reqwest::Client;
use serde_json::json;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let diagnosis: serde_json::Value = Client::new()
.post("https://api.retab.com/v1/workflows/wf_abc123/diagnose-graph")
.bearer_auth(std::env::var("RETAB_API_KEY")?)
.json(&json!({ "blocks": [], "edges": [] }))
.send()
.await?
.error_for_status()?
.json()
.await?;
for issue in diagnosis["issues"].as_array().into_iter().flatten() {
println!(
"[{}] {}: {}",
issue["severity"].as_str().unwrap_or("unknown"),
issue["code"].as_str().unwrap_or("unknown"),
issue["message"].as_str().unwrap_or("")
);
}
Ok(())
}
<?php
require 'vendor/autoload.php';
use Retab\Client;
$client = new Client(apiKey: getenv('RETAB_API_KEY'));
$result = $client->workflows()->diagnose(
workflowId: 'wf_abc123',
);
print_r($result);
using System.Collections.Generic;
using System.Net.Http;
using Retab;
using RetabClient = Retab.Retab;
var apiKey = Environment.GetEnvironmentVariable("RETAB_API_KEY")!;
var client = new RetabClient(apiKey);
var result = await client.MakeAPIRequest<Dictionary<string, object>>(
new RetabRequest
{
Method = HttpMethod.Post,
Path = "/v1/workflows/wf_abc123/diagnose-graph",
},
default
);
Console.WriteLine(result);
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
public final class Example {
public static void main(String[] args) throws Exception {
HttpRequest request =
HttpRequest.newBuilder(URI.create("https://api.retab.com/v1/workflows/wf_abc123/diagnose-graph"))
.header("Accept", "application/json")
.header("Authorization", "Bearer " + System.getenv("RETAB_API_KEY"))
.header("Content-Type", "application/json")
.method("POST", HttpRequest.BodyPublishers.ofString("{}"))
.build();
HttpResponse<String> response =
HttpClient.newHttpClient().send(request, HttpResponse.BodyHandlers.ofString());
System.out.println(response.body());
}
}
# Direct call: pass the graph payload yourself.
curl -X 'POST' \
'https://api.retab.com/v1/workflows/wf_abc123/diagnose-graph' \
-H 'Content-Type: application/json' \
-H 'Authorization: Bearer <your-api-key>' \
-d '{
"blocks": [
{
"id": "start-1",
"type": "start_document",
"label": "Start",
"config": null,
"position": { "x": 0, "y": 0 }
}
],
"edges": [],
"re_propagate": true
}'
{
"is_valid": true,
"issues": [],
"suggestions": [],
"stats": {
"total_blocks": 5,
"total_edges": 4,
"block_types": { "start": 1, "extract": 2, "classifier": 1, "split": 1 },
"start_document_blocks": 1
}
}
{
"is_valid": true,
"issues": [
{
"severity": "warning",
"code": "MISSING_REVIEW_PREDICATE",
"message": "Block 'Extract' has review enabled but no predicate configured.",
"block_id": "extract-1"
}
],
"suggestions": [],
"stats": {
"total_blocks": 2,
"total_edges": 1,
"block_types": { "start": 1, "extract": 1 },
"start_document_blocks": 1
}
}
{
"is_valid": false,
"issues": [
{
"severity": "error",
"code": "NO_START_BLOCK",
"message": "Workflow has no start_document block.",
"block_id": null
},
{
"severity": "warning",
"code": "UNREACHABLE_BLOCK",
"message": "Block 'extract-2' is unreachable from any start_document block.",
"block_id": "extract-2"
}
],
"suggestions": [
"Add a `start_document` block, or remove the orphaned `extract-2` block."
],
"stats": {
"total_blocks": 4,
"total_edges": 2,
"block_types": { "extract": 2, "classifier": 1, "split": 1 },
"start_document_blocks": 0
}
}