retab-dev/retab.
Install
pip install retab
npm install @retab/node
go get github.com/retab-dev/retab/clients/go
gem install retab
composer require retab/retab
dotnet add package Retab
cargo add retab
# Maven
mvn dependency:get -Dartifact=com.retab:retab:0.0.12
# Gradle
gradle dependencies --configuration implementation --dependency com.retab:retab:0.0.12
Authenticate
Each SDK readsRETAB_API_KEY from the environment by default. Get a key from the dashboard settings.
Use RETAB_API_KEY=rt_test_... for development and CI, and
RETAB_API_KEY=rt_live_... for production. The key prefix selects the
customer environment — RETAB_BASE_URL is the
Retab deployment URL, not the environment selector. Legacy sk_retab_...
keys still work and resolve to production.
# Development / CI
export RETAB_API_KEY=rt_test_…
# Production
export RETAB_API_KEY=rt_live_…
from retab import Retab
client = Retab() # uses RETAB_API_KEY
import { Retab } from "@retab/node";
const client = new Retab({ apiKey: process.env.RETAB_API_KEY });
import retab "github.com/retab-dev/retab/clients/go"
client, _ := retab.NewClient("") // empty string → reads RETAB_API_KEY
_ = client
require 'retab'
client = Retab::Client.new(api_key: ENV['RETAB_API_KEY'])
<?php
require 'vendor/autoload.php';
use Retab\Client;
$client = new Client(apiKey: getenv('RETAB_API_KEY'));
using Retab;
using RetabClient = Retab.Retab;
var client = new RetabClient(Environment.GetEnvironmentVariable("RETAB_API_KEY"));
use retab::Retab;
let client = Retab::new(std::env::var("RETAB_API_KEY")?);
import com.retab.RetabClient;
import com.retab.workflowevalruns.WorkflowEvalRunsApi;
public final class Example {
public static void main(String[] args) {
RetabClient client = new RetabClient(System.getenv("RETAB_API_KEY"));
System.out.println(client.workflows() != null);
}
}
Workflow Graph Resources
Blocks and edges are flat workflow resources keyed byworkflow_id. SDK calls
take the workflow id explicitly instead of nesting graph resources under a
/workflows/{workflow_id}/... path.
When creating or updating workflow blocks with a json_schema config, the
public API treats the schema structurally. JSON object property order is not
preserved as authored display order; the dashboard may preserve authored order
through internal editor state.
block = client.workflows.blocks.create(
workflow_id="wf_abc123",
id="blk_extract_1",
type="extract",
label="Extract invoice",
config={
"model": "retab-small",
"json_schema": {
"type": "object",
"properties": {"invoice_number": {"type": "string"}},
"required": ["invoice_number"],
},
},
)
edge = client.workflows.edges.create(
workflow_id="wf_abc123",
id="edge_start_extract",
source_block="start",
target_block=block.id,
)
const block = await client.workflows.blocks.create("wf_abc123", "extract", "blk_extract_1", "Extract invoice", undefined, undefined, undefined, undefined, {
model: "retab-small",
json_schema: {
type: "object",
properties: { invoice_number: { type: "string" } },
required: ["invoice_number"],
},
});
const edge = await client.workflows.edges.create("wf_abc123", "start", block.id, "edge_start_extract");
require 'retab'
client = Retab::Client.new(api_key: ENV['RETAB_API_KEY'])
block = client.workflows.blocks.create(
workflow_id: 'wf_abc123',
id: 'blk_extract_1',
type: 'extract',
label: 'Extract invoice',
config: {
'model' => 'retab-small',
'json_schema' => {
'type' => 'object',
'properties' => { 'invoice_number' => { 'type' => 'string' } },
'required' => ['invoice_number'],
},
},
)
edge = client.workflows.edges.create(
workflow_id: 'wf_abc123',
id: 'edge_start_extract',
source_block: 'start',
target_block: block.id,
)
<?php
use Retab\Resource\WorkflowBlockCreateRequestType;
$block = $client->workflows()->blocks()->create(
workflowId: 'wf_abc123',
type: WorkflowBlockCreateRequestType::Extract,
id: 'blk_extract_1',
label: 'Extract invoice',
config: [
'model' => 'retab-small',
'json_schema' => [
'type' => 'object',
'properties' => ['invoice_number' => ['type' => 'string']],
'required' => ['invoice_number'],
],
],
);
$edge = $client->workflows()->edges()->create(
workflowId: 'wf_abc123',
sourceBlock: 'start',
targetBlock: $block->id,
id: 'edge_start_extract',
);
using System.Collections.Generic;
using Retab;
using RetabClient = Retab.Retab;
var client = new RetabClient(Environment.GetEnvironmentVariable("RETAB_API_KEY"));
var block = await client.Workflows.Blocks.CreateAsync(
new WorkflowBlocksCreateOptions
{
WorkflowId = "wf_abc123",
Id = "blk_extract_1",
Type = WorkflowBlockCreateRequestType.Extract,
Label = "Extract invoice",
Config = new Dictionary<string, object>
{
["model"] = "retab-small",
["json_schema"] = new Dictionary<string, object>
{
["type"] = "object",
["properties"] = new Dictionary<string, object>
{
["invoice_number"] = new Dictionary<string, object> { ["type"] = "string" },
},
["required"] = new List<string> { "invoice_number" },
},
},
}
);
var edge = await client.Workflows.Edges.CreateAsync(
new WorkflowEdgesCreateOptions
{
WorkflowId = "wf_abc123",
Id = "edge_start_extract",
SourceBlock = "start",
TargetBlock = block.Id,
}
);
package main
import (
"context"
"log"
retab "github.com/retab-dev/retab/clients/go"
)
func main() {
client, err := retab.NewClient("")
if err != nil {
log.Fatal(err)
}
blockID := "blk_extract_1"
label := "Extract invoice"
block, err := client.Workflows.Blocks.Create(context.Background(), &retab.WorkflowBlocksCreateParams{
WorkflowID: "wf_abc123",
ID: &blockID,
Type: retab.WorkflowBlockCreateRequestTypeExtract,
Label: &label,
Config: &map[string]any{
"model": "retab-small",
"json_schema": map[string]any{
"type": "object",
"properties": map[string]any{
"invoice_number": map[string]any{"type": "string"},
},
},
},
})
if err != nil {
log.Fatal(err)
}
edgeID := "edge_start_extract"
_, err = client.Workflows.Edges.Create(context.Background(), &retab.WorkflowEdgesCreateParams{
WorkflowID: "wf_abc123",
ID: &edgeID,
SourceBlock: "start",
TargetBlock: block.ID,
})
if err != nil {
log.Fatal(err)
}
}
use retab::{
enums::WorkflowBlockCreateRequestType,
models::{WorkflowBlockCreateRequest, WorkflowEdgeCreateRequest},
resources::{workflow_blocks, workflow_edges},
Retab,
};
let client = Retab::new(std::env::var("RETAB_API_KEY")?);
let mut block_request = WorkflowBlockCreateRequest::new("wf_abc123", WorkflowBlockCreateRequestType::Extract);
block_request.id = Some("blk_extract_1".to_string());
block_request.label = Some("Extract invoice".to_string());
let block = client
.workflows().blocks()
.create(workflow_blocks::CreateParams::new(block_request))
.await?;
let mut edge_request = WorkflowEdgeCreateRequest::new("wf_abc123", "start", &block.id);
edge_request.id = Some("edge_start_extract".to_string());
client
.workflows().edges()
.create(workflow_edges::CreateParams::new(edge_request))
.await?;
import com.retab.RetabClient;
import com.retab.workflowevalruns.WorkflowEvalRunsApi;
public final class Example {
public static void main(String[] args) throws Exception {
RetabClient client = new RetabClient(System.getenv("RETAB_API_KEY"));
var result = client.workflows().blocks().create("wf_abc123", null, null, null, null, null, null, null, null, null);
System.out.println(result);
}
}
Workflow Reviews
Reviews use immutable versions. To submit a corrected output, create a new version underclient.workflows.reviews.versions and pass the reviewed
parent_id/parentVersionId; SDK-created versions are always corrections of an
existing version.
review = client.workflows.reviews.get("rev_abc123")
versions = client.workflows.reviews.versions.list(review_id=review.id)
corrected = client.workflows.reviews.versions.create(
review_id=review.id,
parent_id=versions.data[0].id,
snapshot={"invoice_number": "INV-1001", "total": 1200},
note="fixed total",
)
decision = client.workflows.reviews.approve(
review.id,
version_id=corrected.id,
)
const review = await client.workflows.reviews.get("rev_abc123");
const versions = await client.workflows.reviews.versions.list({
reviewId: review.id,
});
const corrected = await client.workflows.reviews.versions.create(review.id, undefined, { invoice_number: "INV-1001", total: 1200 }, "fixed total");
const decision = await client.workflows.reviews.approve(review.id, corrected.id);
require 'retab'
client = Retab::Client.new(api_key: ENV['RETAB_API_KEY'])
review = client.workflows.reviews.get(review_id: 'rev_abc123')
versions = client.workflows.reviews.versions.list(review_id: review.id)
corrected = client.workflows.reviews.versions.create(
review_id: review.id,
parent_id: versions.data[0].id,
snapshot: { 'invoice_number' => 'INV-1001', 'total' => 1200 },
note: 'fixed total',
)
decision = client.workflows.reviews.approve(
review_id: review.id,
version_id: corrected.id,
)
<?php
$review = $client->workflows()->reviews()->get('rev_abc123');
$versions = $client->workflows()->reviews()->versions()->list(reviewId: $review->id);
$corrected = $client->workflows()->reviews()->versions()->create(
reviewId: $review->id,
parentId: $versions->data[0]->id,
snapshot: ['invoice_number' => 'INV-1001', 'total' => 1200],
note: 'fixed total',
);
$decision = $client->workflows()->reviews()->approve(
$review->id,
versionId: $corrected->id,
);
using System.Collections.Generic;
using Retab;
using RetabClient = Retab.Retab;
var client = new RetabClient(Environment.GetEnvironmentVariable("RETAB_API_KEY"));
var review = await client.Workflows.Reviews.GetAsync("rev_abc123");
var versions = await client.Workflows.Reviews.Versions.ListAsync(
new WorkflowReviewVersionsListOptions { ReviewId = review.Id }
);
var corrected = await client.Workflows.Reviews.Versions.CreateAsync(
new WorkflowReviewVersionsCreateOptions
{
ReviewId = review.Id,
ParentId = versions.Data[0].Id,
Snapshot = new Dictionary<string, object>
{
["invoice_number"] = "INV-1001",
["total"] = 1200,
},
Note = "fixed total",
}
);
var decision = await client.Workflows.Reviews.ApproveAsync(
review.Id,
new WorkflowReviewsApproveOptions { VersionId = corrected.Id }
);
package main
import (
"context"
"log"
retab "github.com/retab-dev/retab/clients/go"
)
func main() {
client, err := retab.NewClient("")
if err != nil {
log.Fatal(err)
}
reviewID := "rev_abc123"
versions, err := client.Workflows.Reviews.Versions.List(context.Background(), &retab.WorkflowReviewVersionsListParams{ReviewID: reviewID})
if err != nil {
log.Fatal(err)
}
note := "fixed total"
corrected, err := client.Workflows.Reviews.Versions.Create(context.Background(), &retab.WorkflowReviewVersionsCreateParams{
ReviewID: reviewID,
ParentID: versions.Data[0].ID,
Snapshot: map[string]any{"invoice_number": "INV-1001", "total": 1200},
Note: ¬e,
})
if err != nil {
log.Fatal(err)
}
_, err = client.Workflows.Reviews.Approve(context.Background(), reviewID, &retab.WorkflowReviewsApproveParams{VersionID: corrected.ID})
if err != nil {
log.Fatal(err)
}
}
use retab::{
models::{ApproveReviewRequest, CreateReviewVersionRequest},
resources::{workflow_review_versions, workflow_reviews},
Retab,
};
use std::collections::HashMap;
let client = Retab::new(std::env::var("RETAB_API_KEY")?);
let review_id = "rev_abc123";
let versions = client
.workflows()
.reviews()
.versions()
.list(workflow_review_versions::ListParams::new(review_id))
.await?;
let mut snapshot = HashMap::new();
snapshot.insert("invoice_number".to_string(), serde_json::json!("INV-1001"));
snapshot.insert("total".to_string(), serde_json::json!(1200));
let corrected = client
.workflows()
.reviews()
.versions()
.create(workflow_review_versions::CreateParams::new(CreateReviewVersionRequest::new(
review_id,
&versions.data[0].id,
snapshot,
)))
.await?;
let decision = client
.workflows()
.reviews()
.approve(
review_id,
workflow_reviews::ApproveParams::new(ApproveReviewRequest::new(&corrected.id)),
)
.await?;
import com.retab.RetabClient;
import com.retab.workflowevalruns.WorkflowEvalRunsApi;
public final class Example {
public static void main(String[] args) throws Exception {
RetabClient client = new RetabClient(System.getenv("RETAB_API_KEY"));
var result = client.workflows().reviews().get("review_abc123");
System.out.println(result);
}
}
Workflow Eval and Experiment Runs
Workflow runs, workflow-eval runs, and experiment runs all use the same run-id-first pattern in the SDKs: create a run from the parent resource, poll the run by itsid, then inspect child records under that run.
eval_run = client.workflows.evals.runs.create(
workflow_id="wf_abc123",
eval_id="eval_abc",
)
eval_run = client.workflows.evals.runs.get(eval_run.id)
eval_results = client.workflows.evals.runs.results.list(eval_run.id)
experiment_run = client.workflows.experiments.runs.create(
workflow_id="wf_abc123",
experiment_id="exp_abc",
)
experiment_run = client.workflows.experiments.runs.get(experiment_run.id)
experiment_results = client.workflows.experiments.runs.results.list(experiment_run.id)
metrics = client.workflows.experiments.runs.metrics.get(
experiment_run.id,
view="summary",
)
const evalRun = await client.workflows.evals.runs.create({
workflowId: "wf_abc123",
evalId: "eval_abc",
});
const currentEvalRun = await client.workflows.evals.runs.get(evalRun.id);
const evalResults = await client.workflows.evals.runs.results.list(evalRun.id);
const experimentRun = await client.workflows.experiments.runs.create("exp_abc", "wf_abc123");
const currentExperimentRun = await client.workflows.experiments.runs.get(experimentRun.id);
const experimentResults = await client.workflows.experiments.runs.results.list(experimentRun.id);
const metrics = await client.workflows.experiments.runs.metrics.get({
runId: experimentRun.id,
view: "summary",
});
require 'retab'
client = Retab::Client.new(api_key: ENV['RETAB_API_KEY'])
eval_run = client.workflows.evals.runs.create(
workflow_id: 'wf_abc123',
eval_id: 'eval_abc',
)
eval_run = client.workflows.evals.runs.get(run_id: eval_run.id)
eval_results = client.workflows.evals.runs.results.list(run_id: eval_run.id)
experiment_run = client.workflows.experiments.runs.create(
workflow_id: 'wf_abc123',
experiment_id: 'exp_abc',
)
experiment_run = client.workflows.experiments.runs.get(run_id: experiment_run.id)
experiment_results = client.workflows.experiments.runs.results.list(run_id: experiment_run.id)
metrics = client.workflows.experiments.runs.metrics.get(
run_id: experiment_run.id,
view: 'summary',
)
<?php
use Retab\Resource\ExperimentRunMetricsView;
$evalRun = $client->workflows()->evals()->runs()->create(
workflowId: 'wf_abc123',
evalId: 'eval_abc',
);
$evalRun = $client->workflows()->evals()->runs()->get($evalRun->id);
$evalResults = $client->workflows()->evals()->results()->list(runId: $evalRun->id);
$experimentRun = $client->workflows()->experiments()->runs()->create(
experimentId: 'exp_abc',
workflowId: 'wf_abc123',
);
$experimentRun = $client->workflows()->experiments()->runs()->get($experimentRun->id);
$experimentResults = $client->workflows()->experiments()->results()->list(runId: $experimentRun->id);
$metrics = $client->workflows()->experiments()->metrics()->get(
runId: $experimentRun->id,
view: ExperimentRunMetricsView::Summary,
);
using Retab;
using RetabClient = Retab.Retab;
var client = new RetabClient(Environment.GetEnvironmentVariable("RETAB_API_KEY"));
var evalRun = await client.Workflows.Evals.Runs.CreateAsync(
new WorkflowEvalRunsCreateOptions
{
WorkflowId = "wf_abc123",
Scope = new WorkflowEvalRunSingleScope { EvalId = "eval_abc" },
}
);
evalRun = await client.Workflows.Evals.Runs.GetAsync(evalRun.Id);
var evalResults = await client.Workflows.Evals.Results.ListAsync(
new WorkflowEvalRunResultsListOptions { RunId = evalRun.Id }
);
var experimentRun = await client.Workflows.Experiments.Runs.CreateAsync(
new ExperimentRunsCreateOptions
{
WorkflowId = "wf_abc123",
ExperimentId = "exp_abc",
}
);
experimentRun = await client.Workflows.Experiments.Runs.GetAsync(experimentRun.Id);
var experimentResults = await client.Workflows.Experiments.Results.ListAsync(
new ExperimentRunResultsListOptions { RunId = experimentRun.Id }
);
var metrics = await client.Workflows.Experiments.Metrics.GetAsync(
new ExperimentRunMetricsGetOptions { RunId = experimentRun.Id }
);
package main
import (
"context"
"fmt"
"log"
retab "github.com/retab-dev/retab/clients/go"
)
func main() {
client, err := retab.NewClient("")
if err != nil {
log.Fatal(err)
}
evalID := "eval_abc"
scope := retab.WorkflowEvalRunScope{
Type: retab.WorkflowEvalRunScopeTypeSingle,
EvalID: &evalID,
}
evalRun, err := client.Workflows.Evals.Runs.Create(context.Background(), &retab.WorkflowEvalRunsCreateParams{
WorkflowID: "wf_abc123",
Scope: &scope,
})
if err != nil {
log.Fatal(err)
}
evalResults, err := client.Workflows.Evals.Results.List(context.Background(), &retab.WorkflowEvalRunResultsListParams{RunID: evalRun.ID})
if err != nil {
log.Fatal(err)
}
workflowID := "wf_abc123"
experimentRun, err := client.Workflows.Experiments.Runs.Create(context.Background(), &retab.ExperimentRunsCreateParams{
WorkflowID: &workflowID,
ExperimentID: "exp_abc",
})
if err != nil {
log.Fatal(err)
}
metrics, err := client.Workflows.Experiments.Metrics.Get(context.Background(), &retab.ExperimentRunMetricsGetParams{RunID: experimentRun.ID})
if err != nil {
log.Fatal(err)
}
fmt.Println(evalResults, metrics)
}
use retab::{
models::{CreateExperimentRunRequest, CreateWorkflowEvalRunRequest},
resources::{experiment_run_metrics, experiment_runs, workflow_eval_run_results, workflow_eval_runs},
Retab,
};
let client = Retab::new(std::env::var("RETAB_API_KEY")?);
let eval_run = client
.workflows()
.evals()
.runs()
.create(workflow_eval_runs::CreateParams::new(
CreateWorkflowEvalRunRequest::new("wf_abc"),
))
.await?;
let eval_results = client
.workflows()
.evals()
.results()
.list(workflow_eval_run_results::ListParams::new(&eval_run.id))
.await?;
let experiment_run = client
.workflows()
.experiments()
.runs()
.create(experiment_runs::CreateParams::new(CreateExperimentRunRequest::new("exp_abc")))
.await?;
let metrics = client
.workflows()
.experiments()
.metrics()
.get(experiment_run_metrics::GetParams::new(&experiment_run.id))
.await?;
println!("{eval_results:#?} {metrics:#?}");
import com.retab.RetabClient;
import com.retab.workflowevalruns.WorkflowEvalRunsApi;
public final class Example {
public static void main(String[] args) throws Exception {
RetabClient client = new RetabClient(System.getenv("RETAB_API_KEY"));
var result = new WorkflowEvalRunsApi(client).create("wf_abc123", null);
System.out.println(result);
}
}
job_id or batch_id handles. Use the
returned run id for polling, cancellation, results, and experiment metrics.