> ## Documentation Index
> Fetch the complete documentation index at: https://docs.retab.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Diagnose Workflow Graph

Validate workflow blocks and edges before publishing. You can pass a draft
graph directly to this endpoint.

The check covers:

* 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

Severities:

| 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:

<RequestExample>
  ```python Python theme={null}
  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']}")
  ```

  ```typescript TypeScript theme={null}
  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}`);
  }
  ```

  ```go Go theme={null}
  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)
  }
  ```

  ```ruby Ruby theme={null}
  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
  ```

  ```rust Rust theme={null}
  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 PHP theme={null}
  <?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);
  ```

  ```csharp C# theme={null}
  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);
  ```

  ```java Java theme={null}
  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());
    }
  }
  ```

  ```curl cURL theme={null}
  # 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
    }'
  ```
</RequestExample>

<ResponseExample>
  ```json 200 (valid) theme={null}
  {
    "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
    }
  }
  ```

  ```json 200 (valid with warnings) theme={null}
  {
    "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
    }
  }
  ```

  ```json 200 (with issues) theme={null}
  {
    "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
    }
  }
  ```
</ResponseExample>
