Skip to content

Clients

The server speaks HTTP on port 8000 and gRPC on port 50051 from a single binary. HTTP is the default and what 95% of users want — the recipes below cover persistent connections and fan-out. gRPC lives in the Advanced section at the bottom of the page.

Every endpoint, query parameter, and response schema lives in the API Reference. This page covers what the reference cannot: keep-alive setup, concurrent fan-out, and protobuf codegen.

HTTP

Use a long-lived client object. A loop of fresh requests pays the TCP handshake every time and can overwhelm the server. All standard HTTP libraries reuse connections by default if you reuse the client.

Python SDK

Install pip install turboocr to get a typed client with retries built in. Full reference at the SDK docs.

from turboocr import Client
with Client(
base_url="http://localhost:8000",
api_key="tocr_live_...",
) as client:
response = client.recognize_image("invoice.jpg")
print(response.results[0].text)

Python

Without the SDK, build one requests.Session at startup and share it. The session pools TCP connections and reuses them across calls, so a tight loop pays the handshake exactly once.

Terminal window
pip install "requests>=2.32"
import requests
SESSION = requests.Session()
SESSION.headers.update({"Connection": "keep-alive"})
BASE_URL = "http://localhost:8000"
def ocr_raw(path: str, layout: bool = False) -> dict:
with open(path, "rb") as f:
data = f.read()
r = SESSION.post(
f"{BASE_URL}/ocr/raw",
data=data,
headers={"Content-Type": "image/png"},
params={"layout": 1} if layout else None,
timeout=30,
)
r.raise_for_status()
return r.json()
def ocr_pdf(path: str, mode: str = "ocr", dpi: int = 100) -> dict:
with open(path, "rb") as f:
data = f.read()
r = SESSION.post(
f"{BASE_URL}/ocr/pdf",
data=data,
params={"mode": mode, "dpi": dpi},
timeout=120,
)
r.raise_for_status()
return r.json()
print(ocr_raw("invoice.png"))

For fan-out, push the same SESSION through a ThreadPoolExecutor. The session is thread-safe for the simple POST pattern below, and the worker count caps how many requests the server sees in flight.

from concurrent.futures import ThreadPoolExecutor
def ocr_many(paths: list[str], workers: int = 8) -> list[dict]:
with ThreadPoolExecutor(max_workers=workers) as pool:
return list(pool.map(ocr_raw, paths))
results = ocr_many(["a.png", "b.png", "c.png", "d.png"])

Advanced — gRPC

Use gRPC if you need streaming, smaller wire size at very high QPS, or you already standardize on protobuf across your services. For most workloads, HTTP is simpler and equally fast. The service definition lives in ocr.proto — download it and run the codegen for your language.

Python (gRPC)

Terminal window
pip install "grpcio>=1.68" "grpcio-tools>=1.68" "protobuf>=5.28"
python -m grpc_tools.protoc -I proto \
--python_out=. --grpc_python_out=. \
proto/ocr.proto

That emits ocr_pb2.py and ocr_pb2_grpc.py. Reuse a single channel per process; it pools HTTP/2 streams internally.

import grpc
from concurrent.futures import ThreadPoolExecutor
import ocr_pb2
import ocr_pb2_grpc
CHANNEL = grpc.insecure_channel(
"localhost:50051",
options=[
("grpc.keepalive_time_ms", 30_000),
("grpc.max_receive_message_length", 50 * 1024 * 1024),
],
)
STUB = ocr_pb2_grpc.OCRServiceStub(CHANNEL)
def recognize(path: str) -> ocr_pb2.OCRResponse:
with open(path, "rb") as f:
return STUB.Recognize(
ocr_pb2.OCRRequest(image=f.read(), layout=False),
timeout=30,
)
def recognize_many(paths: list[str], workers: int = 8):
with ThreadPoolExecutor(max_workers=workers) as pool:
return list(pool.map(recognize, paths))
results = recognize_many(["a.png", "b.png", "c.png"])

For per-endpoint request and response details, see the API Reference.