Skip to main content

Model Routing

Retab provides intelligent model routing through three special model identifiers: retab-large, retab-small and retab-micro. These models automatically route your requests to the current best-performing model based on availability, performance, and speed metrics. This means you don’t need to manually update your model selection when new, better-performing models become available—Retab handles the routing for you, ensuring your applications always use the optimal model for your use case.

Sync & Async Client

Retab offers both synchronous and asynchronous client interfaces, making it versatile for different application needs. The asynchronous client (AsyncRetab) is ideal for high-performance, non-blocking applications where multiple tasks run concurrently. For simpler or blocking operations, the synchronous client (Retab) provides a straightforward approach. Here’s how you can use both:
# Async client.
from retab import AsyncRetab


async def parse_document(document):
    client = AsyncRetab()
    result = await client.parses.create(document=document, model="retab-small")
    print(result.output.text)


# Sync client.
from retab import Retab

client = Retab()

result = client.parses.create(document="invoice.pdf", model="retab-small")
print(result.output.text)

// Async client (default)
import { Retab } from '@retab/node';

async function parseDocument(document) {
    const client = new Retab({ apiKey: process.env.RETAB_API_KEY });
    const result = await client.parses.create(undefined, "retab-small");
    console.log(result.output.text);
}

// Using promises
const client = new Retab({ apiKey: process.env.RETAB_API_KEY });

client.parses.create("invoice.pdf", "retab-small")
    .then(result => console.log(result.output.text));
package main

import (
	"context"
	"fmt"
	"log"

	retab "github.com/retab-dev/retab/clients/go"
)

func main() {
	ctx := context.Background()

	client, err := retab.NewClient("")
	if err != nil {
		log.Fatal(err)
	}

	model := "retab-small"
	result, err := client.Parses.Create(ctx, &retab.ParsesCreateParams{
		Document: "invoice.pdf",
		Model:    &model,
	})
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println(result.Output.Text)
}
require 'retab'

# Ruby SDK is synchronous; for concurrent requests use Thread or a concurrency gem
client = Retab::Client.new

result = client.parses.create(document: 'invoice.pdf')
puts result.output.text
<?php
require 'vendor/autoload.php';

use Retab\Client;

// PHP SDK is synchronous; for concurrent requests use Guzzle's promise/pool APIs
$client = new Client();

$result = $client->parses()->create(document: 'invoice.pdf');
echo $result->output->text . PHP_EOL;
use retab::resources::parses::CreateParams;
use retab::Retab;
use std::path::PathBuf;

// The Rust SDK is async; for concurrent requests spawn multiple tasks with tokio.
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let client = Retab::new(std::env::var("RETAB_API_KEY")?);

    let params = CreateParams::new(PathBuf::from("invoice.pdf"));
    let result = client.parses().create(params).await?;
    println!("{}", result.output.text);
    Ok(())
}
using System;
using System.IO;
using System.Threading.Tasks;
using Retab;
using RetabClient = Retab.Retab;

// The .NET SDK is async by design; every method returns Task<T>.
var client = new RetabClient("YOUR_API_KEY");

var result = await client.Parses.CreateAsync(
    new ParsesCreateOptions
    {
        Document = new FileInfo("invoice.pdf"),
        Model = "retab-small",
    }
);

Console.WriteLine(result.Output.Text);
import com.retab.RetabClient;

public final class Example {
  public static void main(String[] args) throws Exception {
    RetabClient client = new RetabClient(System.getenv("RETAB_API_KEY"));

    var result = client.parses().create(null, "retab-1.5", null, "Extract the invoice fields", null, null);
    System.out.println(result);
  }
}
Both clients provide the same core functionality for processing documents, running workflows, and managing resources, with the flexibility to match your application’s concurrency model.

Pagination

Many top-level resources have support for bulk fetches via list API methods. For instance, you can list extraction links, list email addresses, and list logs. These list API methods share a common structure, taking at least these four parameters: limit, order, after, and before. Retab utilizes pagination via the after and before parameters. Both parameters take an existing object ID value and return objects in either descending or ascending order by creation time.

Rate Limits

Retab implements rate limiting to ensure stable service for all users. The API uses a rolling window rate limit with the following configuration:
  • 300 requests per 60-second window
  • Applies across the following API endpoints:
    • POST /v1/extractions
    • POST /v1/parses
When you exceed the rate limit, the API will return a 429 Too Many Requests response. The response headers will include:
Status 429 - {'detail': 'Rate limit exceeded. Please try again later.'}
For high-volume applications, we can provide a dedicated plan. Contact us for more information.

Consensus

You can leverage the consensus feature to improve the accuracy of the extraction. The consensus feature is a way to aggregate the results of multiple LLMs to improve the accuracy of the extraction. The consensus principle is simple: Multiple runs should give the same result, if the result is not the same, the LLM is not confident about the result so neither should you. We compute a consensus score for each field. Some additional _consensus_score fields are added to the likelihoods object, they are computed as the average of the consensus scores within some context.
import json
from retab import Retab
from retab.types.extractions import ExtractionRequest

with open("booking_confirmation_json_schema.json", "r") as f:
    json_schema = json.load(f)

client = Retab()

response = client.extractions.create(
    document="booking_confirmation.jpg",
    model="retab-micro",
    json_schema=json_schema,
    n_consensus=10,  # >1 combines results from multiple LLM calls
)

import { Retab } from '@retab/node';
import { readFileSync } from 'fs';

const jsonSchema = JSON.parse(
    readFileSync('booking_confirmation_json_schema.json', 'utf-8')
);

const client = new Retab({ apiKey: process.env.RETAB_API_KEY });

const response = await client.extractions.create(
    'booking_confirmation.jpg',
    jsonSchema,
    'retab-micro',
    undefined,
    10  // This will run and combine the results of 10 calls to the same LLM
);
package main

import (
	"context"
	"encoding/json"
	"fmt"
	"log"
	"os"

	retab "github.com/retab-dev/retab/clients/go"
)

func main() {
	ctx := context.Background()

	client, err := retab.NewClient("")
	if err != nil {
		log.Fatal(err)
	}

	schemaBytes, err := os.ReadFile("booking_confirmation_json_schema.json")
	if err != nil {
		log.Fatal(err)
	}
	var jsonSchema map[string]any
	if err := json.Unmarshal(schemaBytes, &jsonSchema); err != nil {
		log.Fatal(err)
	}

	model := "retab-micro"
	nConsensus := 10
	response, err := client.Extractions.Create(ctx, &retab.ExtractionsCreateParams{
		Document:   "booking_confirmation.jpg",
		Model:      &model,
		JSONSchema: jsonSchema,
		NConsensus: &nConsensus, // This will run and combine the results of 10 calls to the same LLM
	})
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println(response)
}
require 'retab'
require 'json'

json_schema = JSON.parse(File.read('booking_confirmation_json_schema.json'))

client = Retab::Client.new

response = client.extractions.create(
  document: 'booking_confirmation.jpg',
  model: 'retab-micro',
  json_schema: json_schema,
  n_consensus: 10, # >1 combines results from multiple LLM calls
)
<?php
require 'vendor/autoload.php';

use Retab\Client;

$jsonSchema = json_decode(file_get_contents('booking_confirmation_json_schema.json'), true);

$client = new Client();

$response = $client->extractions()->create(
    document: 'booking_confirmation.jpg',
    jsonSchema: $jsonSchema,
    model: 'retab-micro',
    nConsensus: 10, // >1 combines results from multiple LLM calls
);
use retab::resources::extractions::CreateParams;
use retab::Retab;
use std::collections::HashMap;
use std::fs;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let client = Retab::new(std::env::var("RETAB_API_KEY")?);

    let schema_bytes = fs::read("booking_confirmation_json_schema.json")?;
    let json_schema: HashMap<String, serde_json::Value> = serde_json::from_slice(&schema_bytes)?;

    let mut params = CreateParams::new("booking_confirmation.jpg", json_schema);
    params.body.model = Some("retab-micro".into());
    params.body.n_consensus = Some(10); // >1 combines results from multiple LLM calls

    let response = client.extractions().create(params).await?;
    println!("{:?}", response);
    Ok(())
}
using System;
using System.Collections.Generic;
using System.IO;
using System.Threading.Tasks;
using Retab;
using RetabClient = Retab.Retab;
using Newtonsoft.Json;

var jsonSchema = JsonConvert.DeserializeObject<Dictionary<string, object>>(
    await System.IO.File.ReadAllTextAsync("booking_confirmation_json_schema.json")
)!;

var client = new RetabClient("YOUR_API_KEY");

var response = await client.Extractions.CreateAsync(
    new ExtractionsCreateOptions
    {
        Document = new FileInfo("booking_confirmation.jpg"),
        Model = "retab-micro",
        JsonSchema = jsonSchema,
        NConsensus = 10, // >1 combines results from multiple LLM calls
    }
);

Console.WriteLine(response.Output);
{
  "booking_id": null,
  "payment": {
    "total_price": 1500,
    "currency": "EUR"
  },
  "client": {
    "company_name": "Acme Corporation",
    "VAT_number": "GB123456789",
    "city": "Manchester",
    "postal_code": "M1 4WP",
    "country": "GB",
    "code": null,
    "email": "client@acme.com"
  },
  "shipments": [
    {
      "shipment_id": "BC-67890",
      "sender": {
        "company_name": "Acme Corporation",
        "address": {
          "city": "Manchester",
          "postal_code": "M1 4WP",
          "country": "GB",
          "line1": "456 Oak Avenue",
          "line2": "Floor 3"
        },
        "phone_number": "+44 20 7946 0958",
        "email_address": "client@acme.com",
        "pickup_datetime": {
          "date": "2023-05-02",
          "start_time": "08:00:00",
          "end_time": "12:00:00"
        },
        "observations": "The transport involves safety protocols, possibly for hazardous goods."
      },
      "recipient": {
        "company_name": "Beta Industries",
        "address": {
          "city": "Munich",
          "postal_code": "80331",
          "country": "DE",
          "line1": "Uncertain",
          "line2": "Suite 500"
        },
        "phone_number": "+49 89 12345",
        "email_address": "contact@beta-ind.com",
        "delivery_datetime": {
          "date": "2023-05-03",
          "start_time": "10:00:00",
          "end_time": "16:00:00"
        },
        "observations": "Uncertain"
      },
      "goods": {
        "packing": {
          "units": 10,
          "packing_type": "pallet",
          "supplementary_parcels": null,
          "pallets_on_ground": null,
          "number_eur_pallet": null,
          "observation": "Uncertain"
        },
        "dimensions": {
          "loading_meters": null,
          "volume": null
        },
        "weight": null,
        "temperature_infos": {
          "min_temperature": null,
          "max_temperature": null,
          "category": "Frozen"
        },
        "dangerous_goods_infos": [
          {
            "weight": 2000,
            "UN_code": null,
            "UN_label": "Uncertain",
            "UN_class": "3",
            "UN_packing_group": "II",
            "ADR_tunnel_code": "B/E"
          }
        ]
      },
      "transport_constraints": {
        "vehicle_type": "Uncertain",
        "body_type": "Refrigerated",
        "tail_lift": false,
        "crane": null
      }
    }
  ]
}
{
  "booking_id": 1.0,
  "payment": {
    "total_price": 0.88889,
    "currency": 0.8,
    "_consensus_score": 0.84445
  },
  "client": {
    "company_name": 0.9,
    "VAT_number": 0.8579535180399872,
    "city": 0.3,
    "postal_code": 0.3,
    "country": 0.3,
    "code": 1.0,
    "email": 0.1,
    "_consensus_score": 0.53685
  },
  "_consensus_score": 0.72476,
  "shipments": [
    {
      "_consensus_score": 0.51772,
      "shipment_id": 0.5,
      "sender": {
        "company_name": 0.7,
        "address": {
          "city": 0.5,
          "postal_code": 0.7360869868340911,
          "country": 0.5,
          "line1": 0.7935465965677979,
          "line2": 0.6270515867719917,
          "_consensus_score": 0.63134
        },
        "phone_number": 0.9208203821621724,
        "email_address": 0.4,
        "pickup_datetime": {
          "date": 0.2,
          "start_time": 0.2,
          "end_time": 0.2,
          "_consensus_score": 0.2
        },
        "observations": 0.5278386154074294,
        "_consensus_score": 0.56333
      },
      "recipient": {
        "company_name": 0.6,
        "address": {
          "city": 0.7,
          "postal_code": 0.8527968987667864,
          "country": 0.7,
          "line1": 0.15743862396133873,
          "line2": 0.5952886900789938,
          "_consensus_score": 0.6011
        },
        "_consensus_score": 0.36685,
        "phone_number": 0.1,
        "email_address": 0.1,
        "delivery_datetime": {
          "date": 0.6,
          "start_time": 0.7,
          "end_time": 0.7,
          "_consensus_score": 0.66667
        },
        "observations": 0.13333486600852992
      },
      "goods": {
        "packing": {
          "units": 0.33333,
          "_consensus_score": 0.47583,
          "packing_type": 0.3,
          "supplementary_parcels": 0.77778,
          "pallets_on_ground": 0.77778,
          "number_eur_pallet": 0.55556,
          "observation": 0.11051665874425773
        },
        "_consensus_score": 0.58476,
        "dimensions": {
          "loading_meters": 1.0,
          "_consensus_score": 0.88889,
          "volume": 0.77778
        },
        "weight": 0.77778,
        "temperature_infos": {
          "min_temperature": 0.66667,
          "_consensus_score": 0.51111,
          "max_temperature": 0.66667,
          "category": 0.2
        },
        "dangerous_goods_infos": [
          {
            "_consensus_score": 0.27017,
            "weight": 0.1,
            "UN_code": 0.33333,
            "UN_label": 0.1876726334617529,
            "UN_class": 0.5,
            "UN_packing_group": 0.4,
            "ADR_tunnel_code": 0.1
          }
        ]
      },
      "transport_constraints": {
        "vehicle_type": 0.1945964952773603,
        "_consensus_score": 0.57365,
        "body_type": 0.2,
        "tail_lift": 0.9,
        "crane": 1.0
      }
    }
  ]
}
import com.retab.RetabClient;

public final class Example {
  public static void main(String[] args) throws Exception {
    RetabClient client = new RetabClient(System.getenv("RETAB_API_KEY"));

    var result = client.extractions().create(null, null, "retab-1.5", "Extract the invoice fields", 10L, null, null, null, null, null, null);
    System.out.println(result);
  }
}