# 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. Extract feature example ## 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).