# Contributing
Source: https://docs.viscribe.ai/development
Set up the ViscribeAI repository and run the checks used for Python, TypeScript, and documentation changes.
## Repository layout
ViscribeAI is a dual-language SDK. Keep Python and TypeScript behavior aligned
unless a change is intentionally scoped to one package.
* `python/`: Python package, examples, and tests.
* `typescript/`: TypeScript package, examples, and tests.
* `docs/`: Mintlify documentation.
* `CONTRIBUTING.md`: repository contribution guide.
* `ROADMAP.md`: planned capabilities and longer-term direction.
## Local setup
Install repository tooling from the repo root.
```bash theme={null}
npm install
```
Install Python dependencies.
```bash theme={null}
cd python
uv sync
```
Install TypeScript dependencies.
```bash theme={null}
cd typescript
npm install
```
Create a root `.env` file only for local live examples.
```bash theme={null}
OPENAI_API_KEY=sk-your-provider-key
OPENAI_MODEL=gpt-5-mini
```
Do not commit `.env` files or provider credentials.
## Verification
Run the checks that match the files you changed.
```bash theme={null}
cd python
uv run python -m pytest
uv run ruff check .
```
```bash theme={null}
cd typescript
npm test
npm run typecheck
npm run build
npm pack --dry-run
```
Repository hygiene checks:
```bash theme={null}
npm run format:check
git diff --check
```
Use `npm run format` from the repo root to format Markdown, JSON, YAML, and
TypeScript files when needed.
## Pull requests
* Open normal pull requests against `develop`.
* Keep the change focused on one problem or feature.
* Use Conventional Commit wording for PR titles.
* Update docs and examples when public behavior changes.
* Add or update tests for user-facing behavior and non-trivial logic.
* Include the exact verification commands you ran.
## Security
Report security issues by email to `security@viscribe.ai`. Do not open public
issues for security-sensitive reports.
# Extract structured data
Source: https://docs.viscribe.ai/features/extract
Extract schema-shaped data from images using simple fields, JSON Schema, or Pydantic models in Python.
## Overview
`extract` turns an image into structured data. You provide an `output_schema`
and an optional instruction, and ViscribeAI asks the model for strict JSON that
matches that schema.
Use simple fields for quick extraction. Use JSON Schema, or a Pydantic model in
Python, when your application needs richer types or nested objects. In
TypeScript, you can also pass a Zod schema directly.
## Pydantic models
Python can accept a Pydantic model class as `output_schema`.
```python theme={null}
from pydantic import BaseModel, Field
from viscribe.images import extract
class Receipt(BaseModel):
merchant_name: str | None = Field(description="Store or business name")
total_amount: float | None = Field(description="Final total on the receipt")
date: str | None = Field(description="Receipt date if visible")
line_items: list[str] = Field(description="Visible purchased items")
result = extract(
image_path="examples/receipt.png",
output_schema=Receipt,
instruction="Extract the receipt fields visible in the image.",
model_config={"model": "gpt-5-mini"},
)
print(result.data.model_dump())
```
## Simple fields
Simple fields support `text`, `number`, `array_text`, and `array_number`.
ViscribeAI converts them into a strict object schema. A maximum of 10 fields is
supported.
```python theme={null}
result = extract(
image_path="examples/receipt.png",
output_schema=[
{"name": "merchant_name", "type": "text"},
{"name": "total_amount", "type": "number"},
{"name": "line_items", "type": "array_text"},
],
instruction="Extract the receipt fields visible in the image.",
model_config={"model": "gpt-5-mini"},
)
```
```ts theme={null}
import { images, type ExtractField } from "viscribe";
const fields: ExtractField[] = [
{ name: "merchant_name", type: "text" },
{ name: "total_amount", type: "number" },
{ name: "line_items", type: "array_text" },
];
const result = await images.extract({
imagePath: "examples/receipt.png",
outputSchema: fields,
instruction: "Extract the receipt fields visible in the image.",
modelConfig: { model: "gpt-5-mini" },
});
```
## JSON Schema
Use JSON Schema when you need explicit schema control.
```python theme={null}
result = extract(
image_path="examples/receipt.png",
output_schema={
"title": "Receipt",
"type": "object",
"properties": {
"merchant_name": {"type": ["string", "null"]},
"total_amount": {"type": ["number", "null"]},
"line_items": {
"type": "array",
"items": {"type": "string"},
},
},
"required": ["merchant_name", "total_amount", "line_items"],
"additionalProperties": False,
},
model_config={"model": "gpt-5-mini"},
)
```
```ts theme={null}
const result = await images.extract({
imagePath: "examples/receipt.png",
outputSchema: {
title: "Receipt",
type: "object",
properties: {
merchant_name: { type: ["string", "null"] },
total_amount: { type: ["number", "null"] },
line_items: {
type: "array",
items: { type: "string" },
},
},
required: ["merchant_name", "total_amount", "line_items"],
additionalProperties: false,
},
modelConfig: { model: "gpt-5-mini" },
});
```
## Zod schemas
TypeScript can accept Zod schemas as `outputSchema`. ViscribeAI converts the
schema to JSON Schema for the model request, then validates the parsed response
with Zod before returning `result.data`.
```ts theme={null}
import { z } from "zod/v4";
import { images } from "viscribe";
const Receipt = z.object({
merchant_name: z.string().nullish(),
total_amount: z.number().nullish(),
line_items: z.array(z.string()),
});
const result = await images.extract({
imagePath: "examples/receipt.png",
outputSchema: Receipt,
instruction: "Extract the receipt fields visible in the image.",
modelConfig: { model: "gpt-5-mini" },
});
console.log(result.data.total_amount);
```
## Result and validation
`result.data` contains the parsed object. Python returns a Pydantic instance when
the output schema is a Pydantic model; otherwise it returns a dictionary.
TypeScript returns the parsed object, or Zod-parsed data when the output schema
is a Zod schema.
With strict mode enabled, ViscribeAI asks the provider for strict structured
output and fills required properties for object schemas. Strict mode is enabled
by default.
Strict schemas are checked against the OpenAI Structured Outputs subset before
the request is sent. Unsupported schemas raise `StructuredOutputSchemaError`
with a schema path and suggested fix.
ViscribeAI also validates parsed model output locally before returning
`result.data`. Invalid output raises `StructuredOutputValidationError`.
Learn how Python and TypeScript validate extracted data against your output schema.
# Output Schema Validation
Source: https://docs.viscribe.ai/features/schema-validation
How ViscribeAI validates parsed image extraction output against your schema in Python and TypeScript.
## Overview
ViscribeAI validates parsed model output before returning `result.data`.
The model is still asked for structured JSON through the provider request, but
the SDK also checks the returned object locally. If the object does not match
the requested schema, ViscribeAI raises `StructuredOutputValidationError`
instead of returning invalid data.
Validation runs after response parsing and before the result wrapper is created.
Malformed JSON is still a parse error, model refusals are still refusal errors,
and incomplete provider responses are still finish-reason errors.
ViscribeAI also checks strict schemas before sending the request. If a schema is
outside the OpenAI Structured Outputs subset, ViscribeAI raises
`StructuredOutputSchemaError` with the schema path and a suggested fix.
## Effective schema
ViscribeAI validates against the same effective schema it uses for structured
output requests.
* Simple fields are converted into an object JSON Schema.
* JSON Schema inputs are validated as JSON Schema.
* Python Pydantic model schemas are validated with Pydantic.
* TypeScript Zod schemas are converted to JSON Schema for the model request
and validated with Zod after parsing.
* Strict mode is enabled by default.
Simple field definitions are intentionally strict: every field is required,
values may be `null`, array fields are capped at five items, and additional
properties are rejected.
When you pass raw JSON Schema with strict mode enabled, ViscribeAI tightens
object schemas by marking object properties as required and rejecting additional
properties when the schema does not already specify that behavior. With
`strict=False` in Python or `strict: false` in TypeScript, ViscribeAI validates
against the JSON Schema you provided without that extra tightening.
For TypeScript Zod schemas, strict mode follows the provider structured-output
rule that object fields must be present. Use nullable fields such as
`z.string().nullish()` when a value may be absent or empty.
## Provider compatibility
With strict mode enabled, ViscribeAI accepts the OpenAI Structured Outputs
subset:
* root schemas must be objects
* nested unions must use `anyOf`; root-level unions are rejected
* every object property must be required
* every object must use `additionalProperties: false`
* arrays must define an `items` schema
* unsupported composition such as `allOf`, `oneOf`, `not`, and conditional
schemas is rejected
* tuple arrays, pattern-based object keys, and uniqueness/member constraints are
left to application validation
* documented schema size limits are checked before the request is sent
Set `strict=False` in Python or `strict: false` in TypeScript only when you need
to bypass this provider-compatibility check for a custom backend.
## Python
Python supports Pydantic models, simple fields, and JSON Schema dictionaries.
Pydantic schemas are validated with `model_validate`. A successful result
returns a Pydantic model instance.
```python theme={null}
from pydantic import BaseModel, Field
from viscribe import StructuredOutputSchemaError, StructuredOutputValidationError
from viscribe.images import extract
class Receipt(BaseModel):
merchant_name: str | None = Field(description="Store or business name")
total_amount: float | None = Field(description="Final receipt total")
try:
result = extract(
image_path="examples/receipt.png",
output_schema=Receipt,
instruction="Extract the visible receipt fields.",
)
print(result.data.model_dump())
except StructuredOutputSchemaError as error:
print(f"Unsupported schema: {error}")
except StructuredOutputValidationError as error:
print(f"Invalid extracted data: {error}")
```
Simple fields and JSON Schema dictionaries are validated with JSON Schema.
A successful result returns a dictionary.
```python theme={null}
from viscribe import StructuredOutputSchemaError, StructuredOutputValidationError
from viscribe.images import extract
try:
result = extract(
image_path="examples/receipt.png",
output_schema=[
{"name": "merchant_name", "type": "text"},
{"name": "total_amount", "type": "number"},
],
instruction="Extract the visible receipt fields.",
)
print(result.data)
except StructuredOutputSchemaError as error:
print(f"Unsupported schema: {error}")
except StructuredOutputValidationError as error:
print(f"Invalid extracted data: {error}")
```
## TypeScript
TypeScript supports simple fields, JSON Schema objects, and Zod schemas.
Simple fields and JSON Schema are validated with AJV. Zod schemas are validated
with Zod, and a successful result returns Zod-parsed data.
```ts theme={null}
import { z } from "zod/v4";
import { images, StructuredOutputSchemaError, StructuredOutputValidationError } from "viscribe";
const Receipt = z.object({
merchant_name: z.string().nullish(),
total_amount: z.number().nullish(),
});
try {
const result = await images.extract({
imagePath: "examples/receipt.png",
outputSchema: Receipt,
instruction: "Extract the visible receipt fields.",
});
console.log(result.data.merchant_name);
} catch (error) {
if (error instanceof StructuredOutputSchemaError) {
console.error("Unsupported schema:", error.message);
}
if (error instanceof StructuredOutputValidationError) {
console.error("Invalid extracted data:", error.message);
}
throw error;
}
```
```ts theme={null}
import { images, StructuredOutputSchemaError, StructuredOutputValidationError, type ExtractField } from "viscribe";
const fields: ExtractField[] = [
{ name: "merchant_name", type: "text" },
{ name: "total_amount", type: "number" },
];
try {
const result = await images.extract({
imagePath: "examples/receipt.png",
outputSchema: fields,
instruction: "Extract the visible receipt fields.",
});
console.log(result.data);
} catch (error) {
if (error instanceof StructuredOutputSchemaError) {
console.error("Unsupported schema:", error.message);
}
if (error instanceof StructuredOutputValidationError) {
console.error("Invalid extracted data:", error.message);
}
throw error;
}
```
## What validation catches
Validation catches schema mismatches such as:
* missing required fields
* values with the wrong type
* array values that exceed simple field limits
* extra object properties when strict mode rejects them
* invalid data returned through SDK-compatible custom clients
Provider-compatibility checks catch schema issues such as:
* root Zod unions or discriminated unions
* raw JSON Schema with `additionalProperties: true`
* unsupported composition such as `allOf` or `oneOf`
* array schemas without `items`
Validation confirms that the output shape matches the schema. It cannot prove that every
extracted value is factually correct, so high-stakes workflows should still include
review, confidence fields, or downstream checks.
# ViscribeAI
Source: https://docs.viscribe.ai/index
Extract structured data from images using AI models.
ViscribeAI is an open-source Python and TypeScript SDK for structured image
extraction.
It exposes one public image method: **`extract`**.
Pass an image, an output schema, and an optional instruction. Viscribe sends an
OpenAI-compatible vision request and returns parsed data that matches your
schema.
Install the SDK and make your first extraction request.
Shape image workflows with schemas and instructions.
## Python
```python theme={null}
from pydantic import BaseModel, Field
from viscribe.images import extract
class Receipt(BaseModel):
merchant_name: str | None = Field(description="Store or business name")
total_amount: float | None = Field(description="Final total on the receipt")
date: str | None = Field(description="Receipt date if visible")
line_items: list[str] = Field(description="Visible purchased items")
result = extract(
image_path="examples/receipt.png",
output_schema=Receipt,
instruction="Extract the receipt fields visible in the image.",
)
print(result.data.model_dump())
```
## TypeScript
```ts theme={null}
import { images } from "viscribe";
const result = await images.extract({
imagePath: "examples/receipt.png",
outputSchema: [
{ name: "merchant_name", type: "text", description: "Store or business name" },
{ name: "total_amount", type: "number", description: "Final total on the receipt" },
{ name: "date", type: "text", description: "Receipt date if visible" },
{ name: "line_items", type: "array_text", description: "Visible purchased items" },
],
instruction: "Extract the receipt fields visible in the image.",
});
console.log(result.data);
```
# Custom client
Source: https://docs.viscribe.ai/model-providers/custom-client
Pass a compatible client instance when your application owns provider transport, authentication, retries, or observability.
## Reuse your client
Pass a custom client when your application already manages provider transport,
authentication, retries, or observability outside ViscribeAI.
The custom client must expose the OpenAI-compatible Chat Completions shape that
ViscribeAI calls internally.
```python theme={null}
from openai import OpenAI
from viscribe import ViscribeAI
openai_client = OpenAI(api_key="sk-your-provider-key")
client = ViscribeAI(
model_config={"model": "gpt-5-mini"},
client=openai_client,
)
result = client.images.extract(
image_path="examples/receipt.png",
output_schema=[{"name": "total_amount", "type": "number"}],
)
```
```ts theme={null}
import OpenAI from "openai";
import { ViscribeAI } from "viscribe";
const openaiClient = new OpenAI({
apiKey: "sk-your-provider-key",
});
const client = new ViscribeAI({
modelConfig: { model: "gpt-5-mini" },
client: openaiClient,
});
const result = await client.images.extract({
imagePath: "examples/receipt.png",
outputSchema: [{ name: "total_amount", type: "number" }],
});
```
## Expected shape
ViscribeAI calls `chat.completions.create` on the client. The response should
match the OpenAI Chat Completions response shape closely enough for ViscribeAI
to read choices, message content or parsed output, refusal data, finish reasons,
and usage metadata.
Use `model_config` or `modelConfig` for model request options even when passing a custom
client.
# OpenAI-compatible
Source: https://docs.viscribe.ai/model-providers/openai-compatible
Configure OpenAI-compatible model providers, credentials, base URLs, and request options for ViscribeAI.
## Bring your provider
ViscribeAI focuses on the image extraction workflow: source handling, strict
structured output, parsing, and typed results. You bring the model provider and
credentials that fit your stack.
The built-in clients use OpenAI-compatible Chat Completions. You can use the
default OpenAI SDK configuration or pass compatible client options for providers
that expose an OpenAI-style API.
Use any vision-capable model provider that exposes an OpenAI-style Chat Completions API
with image inputs and structured output.
## Environment variables
For the default OpenAI client, store credentials in your environment.
```bash theme={null}
export OPENAI_API_KEY="sk-your-provider-key"
export OPENAI_MODEL="gpt-5-mini"
```
`OPENAI_MODEL` is only used by the examples in this repository. In application
code, pass the model explicitly through `model_config` or `modelConfig`.
## Python configuration
```python theme={null}
from viscribe.images import extract
result = extract(
image_path="examples/receipt.png",
output_schema=[{"name": "total_amount", "type": "number"}],
model_config={
"model": "gpt-5-mini",
"api_key": "sk-your-provider-key",
"base_url": "https://example-compatible-provider.com/v1",
"temperature": 1,
"max_retries": 2,
},
)
```
Python client options such as `api_key`, `base_url`, `timeout`, and
`max_retries` are passed to the underlying OpenAI client. Other keys are sent
with the model request.
## TypeScript configuration
```ts theme={null}
import { images } from "viscribe";
const result = await images.extract({
imagePath: "examples/receipt.png",
outputSchema: [{ name: "total_amount", type: "number" }],
modelConfig: {
model: "gpt-5-mini",
apiKey: "sk-your-provider-key",
baseURL: "https://example-compatible-provider.com/v1",
temperature: 1,
maxRetries: 2,
},
});
```
TypeScript client options such as `apiKey`, `baseURL`, `timeout`, and
`maxRetries` are passed to the underlying OpenAI client. Other keys are sent
with the model request.
Use a vision-capable model that supports image inputs and structured output through an
OpenAI-compatible Chat Completions interface.
# Quickstart
Source: https://docs.viscribe.ai/quickstart
Install ViscribeAI and extract structured data from your first image.
## Install
```bash Python theme={null}
pip install viscribe
```
```bash TypeScript theme={null}
npm install viscribe
```
## Configure a model
Viscribe uses OpenAI-compatible chat completions with vision support. Set
`OPENAI_API_KEY` in your environment or pass `api_key` / `apiKey` directly.
```bash theme={null}
export OPENAI_API_KEY=sk-...
export OPENAI_MODEL=gpt-5-mini
```
## Extract structured data
```python Python theme={null}
from pydantic import BaseModel, Field
from viscribe.images import extract
class Receipt(BaseModel):
merchant_name: str | None = Field(description="Store or business name")
total_amount: float | None = Field(description="Final total on the receipt")
date: str | None = Field(description="Receipt date if visible")
line_items: list[str] = Field(description="Visible purchased items")
result = extract(
image_path="examples/receipt.png",
output_schema=Receipt,
instruction="Extract the receipt fields visible in the image.",
model_config={
"model": "gpt-5-mini",
"temperature": 1,
},
)
print(result.data.model_dump())
```
```ts TypeScript theme={null}
import { images } from "viscribe";
const result = await images.extract({
imagePath: "examples/receipt.png",
outputSchema: [
{ name: "merchant_name", type: "text", description: "Store or business name" },
{ name: "total_amount", type: "number", description: "Final total on the receipt" },
{ name: "date", type: "text", description: "Receipt date if visible" },
{ name: "line_items", type: "array_text", description: "Visible purchased items" },
],
instruction: "Extract the receipt fields visible in the image.",
modelConfig: {
model: "gpt-5-mini",
temperature: 1,
},
});
console.log(result.data);
```
## Image inputs
Exactly one image source is required.
```python Python theme={null}
extract(image_path="examples/receipt.png", output_schema=Receipt)
extract(image_url="https://example.com/receipt.png", output_schema=Receipt)
extract(image_base64="iVBORw0KGgo...", output_schema=Receipt)
```
```ts TypeScript theme={null}
await images.extract({
imagePath: "examples/receipt.png",
outputSchema: [{ name: "total_amount", type: "number" }],
});
await images.extract({
imageUrl: "https://example.com/receipt.png",
outputSchema: [{ name: "total_amount", type: "number" }],
});
await images.extract({
imageBase64: "iVBORw0KGgo...",
outputSchema: [{ name: "total_amount", type: "number" }],
});
```
# Social media
Source: https://docs.viscribe.ai/social-media
Find ViscribeAI and itsperini across GitHub, Discord, X, and LinkedIn.
## Community links
Star the repository, open issues, and follow development.
Join the community and discuss ViscribeAI workflows.
Follow itsperini for project updates and notes.
Connect with itsperini on LinkedIn.
## Contact
For direct questions, email [contact@viscribe.ai](mailto:contact@viscribe.ai).