> ## Documentation Index
> Fetch the complete documentation index at: https://docs.stardeck.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Starlens: OCR and document extraction

> Read documents into structured data in your Stardeck app.

Starlens reads photos, PDFs, audio, and video into the fields your app needs. Use it for receipts, invoices, forms, and other documents.

## What makes it useful

Starlens reads document structure as well as characters. It keeps table rows together, returns amounts as numbers, and normalizes dates (including Thai Buddhist-era dates). It can read handwriting and pages with multiple languages. You choose the fields; Starlens returns them in your schema.

## How is it different from OCR?

Basic OCR returns text: `TOTAL ฿482.00`. Starlens can return `total: 482` in a structured receipt, alongside the merchant and line items you asked for.

| | Basic OCR | Starlens |
| - | - | - |
| Output | Text for your app to parse | Fields and rows in your schema |
| Unreadable value | Your app must spot what's missing | `null` plus a reason in `issues` |
| Wrong document | Returns any visible text | Refuses to extract it into the requested shape |

Starlens can read multiple pages and documents in different languages. It keeps extracted names in their original script. It does not match printed names to your app's records or create those records.

## Input and output examples

These illustrative requests use the SDK's actual input shape: `inputs` for the file, `instructions` for document context, and a Zod `schema` with field descriptions. The outputs below show `output` and `issues`; the SDK also returns `version` and `usage`.

**Receipt input:** A phone photo shows `ร้านครัวบ้าน`, `3 ก.ย. 2569`, `ข้าวผัด 2 × ฿60`, and `รวม ฿120`. `receiptUrl` is its signed file URL:

```ts theme={null}
import { createStarlensClient } from "@stardeck-customer-apps/starlens";
import { z } from "zod";

const starlens = createStarlensClient();
const receipt = await starlens.extract({
  version: "v1",
  inputs: [{ url: receiptUrl, filename: "receipt.jpg" }],
  instructions: "Purchase receipt. Copy printed values; do not calculate totals.",
  schema: z.object({
    merchant: z.string().nullable().describe("Shop name at the top, not the payment processor"),
    date: z
      .string()
      .nullable()
      .describe("Purchase date as YYYY-MM-DD; convert Thai Buddhist years"),
    line_items: z.array(
      z.object({
        description: z.string().nullable().describe("Priced item name, not a barcode-only line"),
        quantity: z.number().nullable().describe("Printed quantity"),
        amount: z.number().nullable().describe("Printed line amount"),
      })
    ),
    total: z
      .number()
      .nullable()
      .describe("Printed amount due / grand total / รวม, not a calculated sum"),
  }),
});
```

**Output:**

```json theme={null}
{
  "output": {
    "merchant": "ร้านครัวบ้าน",
    "date": "2026-09-03",
    "line_items": [{ "description": "ข้าวผัด", "quantity": 2, "amount": 120 }],
    "total": 120
  },
  "issues": []
}
```

**Invoice input:** A photo shows supplier `Acme Foods` and total `฿1,250`, but a thumb covers the tax ID. `invoiceUrl` is its signed file URL. The field descriptions give Starlens the printed labels and disambiguation:

```ts theme={null}
const invoice = await starlens.extract({
  version: "v1",
  inputs: [{ url: invoiceUrl, filename: "invoice.jpg" }],
  instructions: "Supplier invoice. Copy printed values; do not calculate totals.",
  schema: z.object({
    supplier: z.string().nullable().describe("Seller's legal entity name, not a brand or logo"),
    tax_id: z
      .string()
      .nullable()
      .describe("Printed Tax ID / TIN / VAT no. / เลขประจำตัวผู้เสียภาษี"),
    total: z.number().nullable().describe("Printed amount due / grand total / รวมทั้งสิ้น"),
  }),
});
```

**Output:**

```json theme={null}
{
  "output": { "supplier": "Acme Foods", "tax_id": null, "total": 1250 },
  "issues": [{ "path": "tax_id", "reason": "A thumb covers the tax ID" }]
}
```

## Add it to your app

Ask the project agent to build the scan and review flow. For example:

```text theme={null}
Add supplier bill scanning. Let staff photograph or upload a bill, then use Starlens
to read the supplier, date, line items, tax, and total. Show the image beside an
editable draft. Highlight unreadable fields, and require review before saving.
Reject files that are not supplier bills.
```

Your app runs Starlens. [**Cloud → Services → Starlens**](https://www.stardeck.ai/projects/~/dashboard/services/starlens) shows past runs, their output, issues, and cost; it is not a place to start a scan. Source files are not stored in the run history.

## Handle uncertain results

If Starlens cannot read a field, it returns `null` and an `issues` entry with the field and reason. Ask for a clearer photo or let someone correct the draft from the source. A `null` without an issue can mean the field was absent.

An empty `issues` list is **not a confidence guarantee**. Check important amounts and IDs against the source before saving. If Starlens refuses the whole file, ask for the right document; do not save an empty record.

## Test with a playground

Ask the project agent to build an extraction or eval playground in your app:

```text theme={null}
Build a Starlens playground for supplier bills. Upload a sample, run the same
schema and version as the real scan flow, and show the source beside the output
and issues. Let me enter correct values and compare them field by field. Do not
save playground results as supplier bills.
```

Try clear, blurry, covered, multi-page, and wrong-type samples. Improve the schema's field descriptions, then rerun the **same samples** to see what changed. Playground runs count toward your AI usage and the daily limit.

## For developers

Call `@stardeck-customer-apps/starlens` from server code with a pinned version (`v1`), a schema, and either publicly fetchable signed URLs or inline file bytes. The platform accepts up to 10 inputs and 40 MB total per request. The SDK returns `output` and `issues`, and raises `UnsupportedDocumentError` for a wrong document type. For the full API and error handling, see the package's `SKILL.md` in your app and [The SDKs](/graviton/sdks).


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.