Getting Started#

Extract structured data from documents programmatically. Responses are structured JSON with extracted fields, coordinates, and confidence scores.

There are four ways in. An official SDK is the recommended path — install it and process a document in a single call, then search and chat over your results.

Raw HTTP calls the same REST API from any language. A drop-in chat UI puts the whole experience inside your own app. And an AI agent can connect over MCP — see the MCP server reference.

Base URLhttps://api.gemina.co
AuthenticationX-API-Key: your-api-key

Official SDKs#

Recommended

Install one package, then process, search & chat in native code — Document Intelligence included.The fastest path for most integrations.

Install#

Add the official Gemina SDK to your project:

npm i @gemina/sdk

Process a document in one call#

Authenticate with your API key and extract structured data — the SDK submits the document and polls for the result for you, so there's no upload-then-poll loop to write:

import { readFile } from "node:fs/promises";
import { GeminaClient } from "@gemina/sdk";

const client = new GeminaClient(process.env.GEMINA_API_KEY!);

// Node: wrap a Buffer in a Blob. In the browser, pass a File from an
// <input type="file"> directly.
const buf = await readFile("./invoice.png");
const result = await client.processDocument(
  new Blob([buf], { type: "image/png" }),
  ["invoice_headers"],
);

const values = result.data?.extractions?.[0]?.values;
console.log("supplier:", values?.vendorName?.value);
console.log("total:", values?.totalAmount?.value);
console.log("date:", values?.invoiceDate?.value);

Add extraction types to a stored document#

Run more extraction types on a document Gemina already stores — no re-upload. The convenience helper submits the request, polls to a terminal result, and returns the whole document with its existing and new extractions. The generated submit-only API accepts anAddExtractionsInDTO, returns aDocumentAddExtractionsOutDTO, and exposes the poll correlation ID when you want to control polling:

// Reuse a documentId from processDocument, FileTag, or a document lookup.
const updated = await client.addExtractionsAndWait(
  documentId,
  ["invoice_line_items"],
  { includeCoordinates: true },
);

console.log(updated.data?.extractions);

// Submit-only generated surface (poll pollCorrelationId yourself):
// client.documents.addDocumentExtractions({
//   documentId,
//   addExtractionsInDTO: { extractionTypes: ["invoice_line_items"] },
// }) -> Promise<DocumentAddExtractionsOutDTO>

Each new type is billed like an upload and must allow the stored document's page count. A type already present is rejected; a purged source must be uploaded again. Adding a type also resets the shared retention date for the whole document to the account's current retention setting.

Query everything you've processed with structured filters, semantic search, or both — and compute exact, database-backed aggregations without re-reading a document:

const { items, meta } = await client.retrieval.retrievalQuery({
  retrievalQueryInDTO: {
    text: "cleaning services invoices from August",
    filters: { totalAmountMin: 1000, currency: "ILS" },
    limit: 10,
  },
});

for (const item of items ?? []) {
  console.log(item.vendorName, item.totalAmount, item.issueDate, item.documentId);
}
console.log(`${meta.count} matches (mode: ${meta.mode})`);

const { rows } = await client.retrieval.retrievalAggregate({
  retrievalAggregateInDTO: {
    metrics: [{ op: "sum", field: "total_amount" }, { op: "count" }],
    groupBy: ["vendor_name"],
  },
});

for (const row of rows ?? []) {
  console.log(row.group, row.values);
}

Chat with your documents#

Ask in natural language and get grounded answers with citations. Follow-up questions keep their conversation context. A conversation's live context expires after roughly 24h of inactivity — reset and resend to continue in a fresh one. The transcript itself is not lost; it stays readable in chat history below:

const reply = await client.chat.chatQuery({
  chatQueryInDTO: { message: "How much did we spend on cleaning in 2020?" },
});

console.log(reply.answer);
console.log("confident:", reply.confident);
console.log("citations:", reply.citations);

const chat = client.conversation();
await chat.send("How much did we spend on cleaning in 2020?");
const follow = await chat.send("And which vendor was most expensive?"); // remembers 2020 / cleaning
console.log(follow.answer, "· session:", chat.sessionId);

await chat.delete(); // end it server-side (or chat.reset() to just forget it locally)

Chat history#

Past conversations are kept as sessions you can list, reread, and purge. Each one carries an auto-generated title, its turn count, the end-user it was scoped to, and the date your retention window will delete it:

const listing = await client.chat.listChatSessions({ limit: 20 });
for (const session of listing.sessions) {
  console.log(session.title, "·", session.turnCount, "turns");
}

const transcript = await client.chat.getChatSession({ sessionId: listing.sessions[0].id });
for (const msg of transcript.messages) {
  console.log(`[${msg.role}] ${msg.content}`);
}

await client.chat.purgeChatSession({ sessionId: listing.sessions[0].id });

Purging permanently deletes the transcript and the server-side copy of its content — it cannot be undone. Purged sessions vanish from the list; ask for purged records to see their content-free stubs (title cleared, purge timestamp and reason set; timestamps, turn count, and end-user id survive). Transcripts also age out automatically under your account's data-retention setting.

Browser-safe session tokens#

Exchange your API key server-side for a short-lived, scoped token so a browser can search and chat without ever seeing the key. Tokens are read-and-chat scoped: they can query, aggregate, chat, read history, and end a conversation — but they can never purge one, which takes an API key or a console sign-in:

// Server-side (holds the API key)
const session = await client.sessions.mintRetrievalToken({
  sessionTokenInDTO: { endUserId: "user-42", ttlSeconds: 900 },
});
// -> { token, expiresAt, expiresIn, tokenType }

// Browser (token only)
import { GeminaClient } from "@gemina/sdk";
const browserClient = GeminaClient.withSessionToken(session.token);
const results = await browserClient.retrieval.retrievalQuery({
  retrievalQueryInDTO: { text: "last month's invoices" },
});

Human verification#

Put a person in front of an extraction before it reaches your workflow. Mint a token scoped to that one extraction, render the drop-in reviewer (see Embed in your app), and read the result back afterwards. The corrections arrive as verifiedValues — the same shape as values, so switching your pipeline to human-verified data is a one-name change — alongside verifiedDiff, the list of what changed:

// Server-side. Authorize the end-user against this id FIRST — Gemina enforces
// the claim in the signed token, you decide who gets it.
const session = await client.sessions.mintRetrievalToken({
  sessionTokenInDTO: {
    extractionIds: [extractionId], // pins the token — up to 10
    ttlSeconds: 900,
  },
});
// Ship session.token to the browser and render <GeminaVerification />.

// The source of truth. The widget's browser callback is best-effort.
const view = await client.documents.getDocumentExtraction({
  documentExtractionId: extractionId,
});

if (view.meta.validated) {
  for (const change of view.verifiedDiff ?? []) {
    // status: "corrected" | "added" | "removed"
    console.log(change.status, change.field, change.original, "->", change.verified);
  }
  useThis(view.verifiedValues); // same shape as values, corrections merged in
} else {
  useThis(view.values); // nobody has reviewed it yet
}

// Only if you build your own review UI. One-shot: a second call is a 409.
const summary = await client.documents.validateDocumentExtraction({
  targetDocumentExtractionId: extractionId,
  extractionValidationInDTO: { data: correctedValues },
});

Verification is one-shot per extraction; a second submission is rejected. Reading the extraction back is the source of truth — the widget's browser callback is best-effort, so if the network drops the response the verification is still recorded but the callback never fires.

Error handling#

Typed errors separate a terminal processing failure from a still-processing timeout you can resume:

import { GeminaProcessingError, ResponseError } from "@gemina/sdk";

try {
  const result = await client.processDocument(file, ["invoice_headers"]);
} catch (err) {
  if (err instanceof GeminaProcessingError) {
    console.error("processing failed:", err.result.errors);
  } else if (err instanceof ResponseError) {
    console.error("HTTP error:", err.response.status);
  } else {
    throw err;
  }
}

Raw HTTP API#

No SDK, or on an unsupported language? Every endpoint is a plain REST call. Search, aggregate, chat & chat history over your processed documents is documented right here in curl; for the upload-and-extract quick start in your language, open the picker below. Response shapes are in Response Fields; what a failure looks like is in Errors.

With Document Intelligence (opt-in per account), every successful extraction is indexed into a searchable layer — query your whole collection with exact filters, natural language, or both, and compute exact totals without re-reading a single document. Every SDK wraps this — see the “Search & analyze”, “Chat”, “Chat history”, and “Session tokens” tabs above; the requests below are the underlying REST API.

One endpoint, three modes: structured (exact filters), semantic (meaning), and hybrid (both, fused — the best default for free text):

# Hybrid search: keywords + meaning (best default for free text)
curl -X POST https://api.gemina.co/api/v1/retrieval/query \
  -H "X-API-Key: your-api-key" \
  -H "Content-Type: application/json" \
  -d '{
    "mode": "hybrid",
    "text": "the credit note about the server outage",
    "topK": 10,
    "filters": { "issueDateFrom": "2026-01-01" }
  }'

Exact aggregations#

Sums, averages and counts are computed in the database — never estimated by an AI model. Amounts in different currencies are never mixed into one total:

# Exact totals per vendor, computed in the database (never estimated by AI)
curl -X POST https://api.gemina.co/api/v1/retrieval/aggregate \
  -H "X-API-Key: your-api-key" \
  -H "Content-Type: application/json" \
  -d '{
    "metrics": [{ "op": "sum", "field": "total_amount" }, { "op": "count" }],
    "groupBy": ["vendor_name", "currency"],
    "filters": { "issueDateFrom": "2026-01-01", "issueDateTo": "2026-03-31" }
  }'

Chat with your documents#

Ask in natural language; Gemina routes the question to the right engine and answers grounded in your documents, with citations:

# Grounded natural-language Q&A over your document collection
curl -X POST https://api.gemina.co/api/v1/chat/query \
  -H "X-API-Key: your-api-key" \
  -H "Content-Type: application/json" \
  -d '{ "message": "How much did we spend at Acme Catering this quarter?" }'
{
  "answer": "You spent a total of 18,450 ILS at Acme Catering this quarter, across 12 invoices.",
  "citations": ["5f2b7c1e-2b6b-4f0e-9a1c-4d3f2e1a0b9c"],
  "intent": "aggregation",
  "confident": true
}

Chat history#

Past conversations are kept as sessions you can list, reread, and purge. Each session carries a short auto-generated title, its turnCount, its endUserId (null = a tenant-wide chat over all documents), and purgeAt — when your data-retention window will delete it (null = kept always):

# List past sessions (newest activity first by default)
curl https://api.gemina.co/api/v1/chat/sessions?limit=20 \
  -H "X-API-Key: your-api-key"
# -> { "count": 3, "sessions": [ { "id": "c1a4e9d2-...", "title": "Cleaning spend 2020",
#      "turnCount": 4, "lastActivityAt": "...", "purgeAt": "...", "purgedAt": null, ... } ] }

# Read one session's transcript (paged with skip/limit)
curl https://api.gemina.co/api/v1/chat/sessions/c1a4e9d2-6b3f-4a71-9e02-... \
  -H "X-API-Key: your-api-key"
# -> { "session": {...}, "count": 8, "messages": [ { "role": "user", "content": "...",
#      "turnIndex": 0, ... }, { "role": "assistant", "content": "...", "confident": true,
#      "citations": [...], ... } ] }

# Permanently purge a session — transcript and server-side content, 204 on success
curl -X DELETE https://api.gemina.co/api/v1/chat/sessions/c1a4e9d2-6b3f-4a71-9e02-.../purge \
  -H "X-API-Key: your-api-key"

Purge is not “New chat”. DELETE /chat/sessions/{sessionId} only ends the live context — the transcript stays in history; DELETE /chat/sessions/{sessionId}/purge permanently deletes the transcript and the server-side copy of its content, and cannot be undone. Purged sessions vanish from the list; pass with_purged=true to see their content-free stubs (title cleared, purgedAt/purgeReason set). Session tokens can list and read history within their pinned scope, but can never purge — that takes an API key, or a console sign-in on the /purge/user variant.

For end-user-facing apps, exchange your API key server-side for a short-lived, scoped session token (POST /api/v1/sessions/token) — the browser can then query and chat safely without ever seeing your API key. The same capabilities are available to AI agents as MCP tools (query_documents, aggregate_documents, index_document).

Errors#

An error uses the same envelope as a success: status is failed, data is null, and the machine-readable detail is one entry in errors. Below is a real response — an unrecognized API key:

{
  "status": "failed",
  "meta": { "externalId": null, "correlationId": null, "userId": null },
  "data": null,
  "errors": [
    {
      "error_code": "ACCESS_DENIED_ERROR",
      "description": "Access Denied: API Key not found"
    }
  ],
  "createdAt": null,
  "createdAtTimestamp": null,
  "servedAt": "2026-08-11T17:01:07.138498",
  "servedAtTimestamp": 1786467667.138507
}

Branch on errors[0].error_code, not on description: the code is stable, the description is written for humans and changes freely. The code keeps its snake_case key even though the envelope around it is camelCase. On the error path meta is null, or carries only the identifiers your request supplied — the richer success meta is not available.

  • 401 UNAUTHORIZED_ERROR — No credential was sent, or a session token is invalid or expired. Mint a new token.
  • 403 ACCESS_DENIED_ERROR — The API key is unknown, revoked or expired, or the account is inactive.
  • 403 DOCUMENT_INTELLIGENCE_NOT_IN_PLAN — Search, aggregation and chat are not enabled on this plan.
  • 404 CHAT_SESSION_NOT_FOUND — The session is unknown, expired after 24h idle, or belongs to someone else. Retry without a sessionId to start a new conversation.
  • 413 REQUEST_ENTITY_TOO_LARGE_ERROR — The file is over 10 MB.
  • 415 UNSUPPORTED_MEDIA_TYPE_ERROR — The format is not one we accept.
  • 422 UNPROCESSABLE_ERROR — The request is malformed: an incompatible option combination, custom_template without a template_id, a file under 1 KB, a semantic query with no text, or a question chat could not process.
  • 422 DOCUMENT_MAX_PAGES_EXCEEDED_ERROR — The PDF is longer than the page limit for the extraction types you asked for.
  • 429 — You hit a rate limit or ran out of credit, and error_code says which. Back off on the Retry-After response header when one is sent. Burst limits (RETRIEVAL_RATE_LIMIT_EXCEEDED, FILETAG_RATE_LIMIT_EXCEEDED) clear in a second; quota and credit exhaustion (QUOTA_EXCEEDED, SPEND_LIMIT_EXCEEDED, CREDIT_EXHAUSTED, INSUFFICIENT_CREDITS, CHAT_QUOTA_EXCEEDED, FILETAG_QUOTA_EXHAUSTED) will not clear by retrying.
  • 502 BAD_GATEWAY_ERROR — The chat backend was unreachable. Retry shortly.

Authentication fails in two different ways. Sending no credential is a 401; sending an API key we don't recognize is a 403, not a 401. Only the session-token path answers 401 for a bad credential — so a client that refreshes on 401 alone will loop forever against a revoked API key.

Size & page limits#

Documents are 1 KB to 10 MB. The page ceiling is per extraction type — ask for several types in one request and the most permissive ceiling applies:

  • custom_template — 30 pages
  • ocr — 15 pages
  • invoice_headers, invoice_line_items — 10 pages

Format, size and page checks all run before processing starts, so a rejected upload costs no credits. Single images aren't paged, so they're measured instead: every upload is normalized to 1240 px wide and each 2420 px of height counts as one page equivalent. A long stitched screenshot can exceed the ceiling that way — unlike a PDF it's caught during processing, so it comes back as a failed extraction rather than a rejected upload.

Embed in your app#

Ship two experiences without building either. @gemina/elements gives you a drop-in React chat component (<GeminaChat>) and a human review-and-correct step (<GeminaVerification>), both behind a security-hardened token manager — citations, confidence handling, and RTL support included, without ever exposing your API key to the browser.

Install#

npm i @gemina/elements @gemina/sdk react

Mint session tokens (Next.js App Router)#

The API key stays on your server. This route mints a short-lived, scoped token for the browser — drop it in as-is:

// Next.js (App Router) — app/api/gemina-session/route.ts
import { NextResponse } from "next/server";
import { GeminaClient } from "@gemina/sdk";

const gemina = new GeminaClient(process.env.GEMINA_API_KEY!);

export async function POST(request: Request) {
  const user = await requireYourAppAuth(request); // your session check
  const minted = await gemina.sessions.mintRetrievalToken({
    sessionTokenInDTO: { endUserId: user.id, ttlSeconds: 900 },
  });
  return NextResponse.json({ token: minted.token, expiresIn: minted.expiresIn });
}

Render the chat#

Point the token manager at that route and render the component — conversation memory and the session token are managed for you. Give it your own voice with title (header text — your brand or assistant persona; tint the bar with --gemina-chat-header-bg) and intro (what the assistant can see, shown in the empty conversation until the first message):

import { GeminaTokenManager } from "@gemina/elements/token-manager";

const tokenManager = new GeminaTokenManager({
  // Points at YOUR backend — see the mint endpoint below.
  fetchToken: async () => {
    const res = await fetch("/api/gemina-session", { method: "POST" });
    if (!res.ok) throw new Error("Failed to mint Gemina session token");
    return res.json(); // { token, expiresIn }
  },
  // Optional: seconds before expiry to refresh (default 60).
  refreshSkewSeconds: 60,
});

import { GeminaChat } from "@gemina/elements";

<GeminaChat
  tokenManager={tokenManager}
  // Header text — your brand or assistant persona. When set, the header
  // is always visible; tint the bar with --gemina-chat-header-bg.
  title="Acme Invoices"
  // Centered in the empty conversation until the first message. Newlines
  // split it into separately spaced paragraphs.
  intro="Answers come from your indexed document data."
  onCitationClick={(documentId) => openDocumentViewer(documentId)}
/>;

Mint an extraction-scoped token#

Verification gets its own mint route. The token is pinned to a single extraction, so a curious end-user with developer tools can't read anything else in your account. Gemina enforces the pin; your endpoint decides who is allowed to ask for it — the extraction id arrives from the browser, so authorize it against your own user before minting:

// Next.js (App Router) — app/api/gemina-verify-session/route.ts
import { NextResponse } from "next/server";
import { GeminaClient } from "@gemina/sdk";

const gemina = new GeminaClient(process.env.GEMINA_API_KEY!);

export async function POST(request: Request) {
  const user = await requireYourAppAuth(request); // your session check
  const { extractionId } = await request.json();

  // YOU decide who may see this extraction. Gemina enforces the claim in the
  // signed token; it cannot know whether this user is entitled to that id.
  if (!(await userMayVerify(user, extractionId))) {
    return NextResponse.json({ error: "Forbidden" }, { status: 403 });
  }

  const minted = await gemina.sessions.mintRetrievalToken({
    sessionTokenInDTO: {
      extractionIds: [extractionId], // pins the token — up to 10
      ttlSeconds: 900,
    },
  });
  return NextResponse.json({ token: minted.token, expiresIn: minted.expiresIn });
}

Render the reviewer#

The document sits next to every extracted field as an editable input. The reviewer corrects what's wrong and submits once. Run the extraction with evaluation enabled and each field also carries a confidence score, with a switch that hides everything already scored high — on a 169-row invoice that is 169 rows down to 7:

import { useMemo } from "react";
import { GeminaVerification } from "@gemina/elements/verification";
import { GeminaTokenManager } from "@gemina/elements/token-manager";

function VerifyStep({ extractionId }: { extractionId: string }) {
  // Stable per extraction — never construct the manager inline in JSX.
  const tokenManager = useMemo(
    () =>
      new GeminaTokenManager({
        fetchToken: async () => {
          const res = await fetch("/api/gemina-verify-session", {
            method: "POST",
            headers: { "Content-Type": "application/json" },
            body: JSON.stringify({ extractionId }),
          });
          if (!res.ok) throw new Error("Failed to mint Gemina session token");
          return res.json(); // { token, expiresIn }
        },
      }),
    [extractionId]
  );

  return (
    <GeminaVerification
      extractionId={extractionId}
      tokenManager={tokenManager}
      // Fires the moment the reviewer submits — good for closing the step.
      // Best-effort: if the network drops the response the verification is
      // still recorded, so read the extraction back for anything that matters.
      onComplete={({ correctedValues, summary }) => {
        closeReviewStep();
      }}
      onError={(reason) => reportToYourMonitoring(reason)}
    />
  );
}

Then read the extraction back for the corrections — verifiedValues and verifiedDiff, shown per language under Human verification in the SDK guides. Submission is final: an extraction can be verified once.

Response Fields#

Raw HTTP responses use the field names below exactly, including snake_case names such as line_items and confidence_reasons. SDKs may expose language-specific property names. Array<T> means an array of T; Map<string, T> means an object with dynamic string keys. JSON value means a string, number, boolean, object, array, or null.

Read data.extractions[] and select each payload by meta.extractionType. values contains the extracted result; verifiedValues contains the reviewer-finalized result with the same shape. Both can be null. A document can contain multiple extraction results with different statuses.

Field<T> is an object with value (T or null), coordinates (Coordinates or null), and confidence (high, medium, low, or null). A field marked Field<T> | null can also have a null envelope. Enum options apply to value for wrapped fields, or to each element for arrays. Unconstrained strings and numbers have no fixed option list.

Missing scalar values are generally null; collections can be empty. Do not treat null as zero or assume every property is populated. Coordinates are available only when requested and found. Creation timestamps can be null. Confidence levels are categorical, not percentages.

The document extraction types documented below are accepted by document uploads. FileTag uses its own endpoint and response reference: https://www.gemina.co/docs/filetag. The metadata options also include filetag because it is a backend extraction type.

Response envelope#

Response envelope: types and allowed values
FieldJSON typeDescription and allowed values
servedAtstring (date-time)Time this response was served (UTC).
servedAtTimestampnumberTime this response was served as Unix seconds.
createdAtstring (date-time) | nullCreation time, when available.
createdAtTimestampnumber | nullCreation time as Unix seconds, when available.
statusstringProcessing outcome. See the status meanings in Interpretation and calculations.
Allowed values: pending, in_process, failed, success, partial, empty
metaDocument metadataIdentifiers and processing metadata for this response.
dataDocument data | nullResponse payload; null when no payload is available.
errorsArray<Map<string, JSON value>>Error objects supplied by the API. Properties vary by error; may include error_code, description, detail, and status_code.

invoice_headers fields#

invoice_headers fields: types and allowed values
FieldJSON typeDescription and allowed values
vendorNameField<string> | nullSupplier name
vendorTaxIdField<string> | nullTax/VAT ID if present
vendorAddressField<string> | nullSupplier address
vendorEmailField<string> | nullSupplier email address
vendorPhoneField<string> | nullSupplier phone number
buyerNameField<string> | nullBuyer name
buyerTaxIdField<string> | nullBuyer Tax/VAT ID if present
buyerAddressField<string> | nullBuyer address
buyerEmailField<string> | nullBuyer email address
buyerPhoneField<string> | nullBuyer phone number
invoiceNumberField<string> | nullInvoice identifier as printed; kept as a string to preserve leading zeros.
invoiceDateField<string> | nullISO date if possible
dueDateField<string> | nullISO date if present
purchaseOrderField<string> | nullPurchase-order reference printed on the invoice.
currencyField<string> | nullISO 4217 code
grossSubtotalAmountField<number> | nullSum of line items BEFORE any header-level discount or rounding
discountAmountField<number> | nullHeader-level discount in document currency, sign preserved as printed
discountPercentageField<number> | nullHeader-level discount percentage
roundingAmountField<number> | nullRounding adjustment, signed as printed
subtotalAmountField<number> | nullTax base — value the VAT/tax percentage is calculated against (after header-level discount + rounding, before tax)
taxesArray<Tax entry>List of all taxes applied to the invoice
totalAmountField<number> | nullTotal amount including taxes and VAT
paymentMethodField<string> | nullPayment method identified on the document.
Allowed values: cheque, wire_transfer, credit_card, paypal, cash, app, other
documentTypeField<string> | nullDocument classification. Allowed values depend on the extraction type.
Allowed values: unknown, invoice, delivery_note, invoice_receipt, receipt, payment_confirmation, donation_receipt, credit_note, quote, proforma_invoice, purchase_order, order_confirmation, debit_note, down_payment_invoice, refund_receipt, account_statement, remittance_advice
expenseTypeField<string> | nullDocument-level expense category, classified from the vendor, title and line descriptions. Categories follow the expense lines shared by IRS Schedule C, HMRC SA103F, QuickBooks and Xero. Coordinates are always null (inferred, not printed).
Allowed values: advertising_and_marketing, bank_and_payment_fees, cost_of_goods_sold, professional_services, insurance, meals_and_entertainment, office_supplies, software_and_subscriptions, rent_and_lease, repairs_and_maintenance, shipping_and_delivery, taxes_and_licenses, travel, vehicle_and_fuel, utilities, telecommunications, equipment_and_fixed_assets, staffing_and_payroll, other
complianceCodeField<string> | nullCountry-specific compliance or regulatory reference number required by local law, such as ATCUD (Portugal), IRN (India), CFDI UUID (Mexico), or allocation number (Israel).
overallConfidencestring | nullOverall confidence in the extraction
Allowed values: high, medium, low
expenseType values
ValueMeaningExamples
advertising_and_marketingAdvertising, promotion and marketing spend.Google Ads or Meta Ads invoice, agency retainer, trade-show sponsorship, printed flyers, website campaign.
bank_and_payment_feesCharges for holding or moving money.Monthly bank account fee, Stripe or PayPal processing fees, loan interest statement, foreign-exchange commission.
cost_of_goods_soldGoods, merchandise and raw materials bought for resale or production, including food, drink and serving disposables delivered to a restaurant, cafe, bar, caterer or shop.Wholesaler delivery note to a grocery, drinks supplier invoice to a bar, packaging and cups for a cafe, raw materials for a workshop.
professional_servicesFees for expert or contracted work.Lawyer, accountant, auditor, consultant, freelance designer, subcontractor, construction contractor.
insuranceAny insurance premium.Liability, property, vehicle or health insurance invoice, policy renewal.
meals_and_entertainmentA meal or hospitality service the buyer consumed, never a supplier delivery of food stock.Restaurant bill, catered team event, client dinner, hotel bar receipt.
office_suppliesStationery, printing, consumables and small office items.Paper and toner, pens, print-shop order, small desk accessories.
software_and_subscriptionsSoftware, cloud services, licences, dues and memberships.Microsoft 365, AWS, Slack, a professional association fee, a trade journal subscription.
rent_and_leasePremises rent and equipment or operating leases.Office or warehouse rent, property management fee, leased copier, co-working membership.
repairs_and_maintenanceRepair, servicing, cleaning and janitorial work or supplies.Plumber, HVAC service, machine servicing, cleaning company, cleaning and hygiene chemicals.
shipping_and_deliveryMoving or storing goods and documents.Courier, freight forwarder, postage, pallet delivery, storage fee.
taxes_and_licensesGovernment fees and charges.Business licence, permit, municipal business rates, customs duties, regulatory filing fee.
travelCosts of a trip.Airfare, hotel, train ticket, taxi or ride-hailing, airport parking, car rental on a trip.
vehicle_and_fuelRunning the buyer's own vehicles.Fuel receipt, tyre replacement, vehicle service, toll charges, vehicle lease running costs.
utilitiesElectricity, water, gas and heating.Electricity bill, water bill, gas bill, district heating.
telecommunicationsPhone, mobile, internet and fax services.Mobile plan, office broadband, VoIP service, fax line.
equipment_and_fixed_assetsAssets bought outright rather than leased or repaired.Laptops, machinery, furniture, tools, a handset bought from a telecom vendor.
staffing_and_payrollStaffing, recruitment and payroll services and employee benefits.Temp agency invoice, recruitment fee, payroll provider, benefits provider.
otherNo category above fits.A donation receipt, a one-off purchase with no clear category.

invoice_line_items fields#

invoice_line_items fields: types and allowed values
FieldJSON typeDescription and allowed values
line_itemsArray<Invoice line item>List of all line items extracted from the invoice
total_linesinteger | nullTotal count of line items
overall_confidencestring | nullOverall confidence in the line items extraction
Allowed values: high, medium, low
confidence_reasonsArray<string>Reasons for the assigned overall confidence level
Allowed values: no_line_evaluations, high_low_line_ratio, some_low_lines, high_line_confidence, mixed_line_confidence, line_count_mismatch, minor_consensus_mismatch, minor_consensus_match

ocr fields#

ocr fields: types and allowed values
FieldJSON typeDescription and allowed values
textstringExtracted document text.
coordinatesMap<string, Array<Coordinates>> | nullDocument location, when requested and available. OCR maps text keys to arrays of coordinate objects.

custom_template fields#

custom_template fields: types and allowed values
FieldJSON typeDescription and allowed values
dataMap<string, JSON value>Dynamic fields and tables defined by your template. Leaves use { value, coordinates }; keys, value types, and allowed values come from the template. See meta.validationFeedback for editable field and column types.

Field envelope#

Field envelope: types and allowed values
FieldJSON typeDescription and allowed values
valueT | nullThe extracted value of type T (the type named inside Field<T>), or null when not found.
coordinatesCoordinates | nullDocument location, when requested and available. OCR maps text keys to arrays of coordinate objects.
confidencestring | nullCategorical confidence, not a numeric probability. Null when unavailable.
Allowed values: high, medium, low

Document metadata#

Document metadata: types and allowed values
FieldJSON typeDescription and allowed values
externalIdstring | nullYour document identifier supplied at upload.
documentIdstring (UUID) | nullGemina identifier for the stored document.
userIdstring (UUID) | nullIdentifier of the account that owns the document.
endUserIdstring | nullOptional end-user identifier supplied by your integration.
apiKeyIdstring (UUID) | nullIdentifier of the API key associated with this document.
filenamestring | nullUploaded document filename.
fileSizeinteger | nullUploaded file size in bytes.
contentTypestring | nullFile MIME type, for example application/pdf.
documentFileTypestring | nullDocument file type.
Allowed values: pdf, image, other
numberOfPagesinteger | nullNumber of pages in the document.
normalizedWidthinteger | nullNormalized document image width in pixels.
normalizedHeightinteger | nullNormalized document image height in pixels.
normalizedMegapixelsnumber | nullNormalized document image size in megapixels.
storageLocationstring | nullRegion where the document is stored.
Allowed values: israel, europe, united_states, asia
imageUrlstring (URL) | nullURL of the rendered document image, when available.
thumbnailUrlstring (URL) | nullURL of the document thumbnail, when available.
originalDocumentUrlstring (URL) | nullURL of the original uploaded file, when available.
correlationIdstring (UUID) | nullIdentifier linking the upload request to its processing results.
nextstring (URL) | nullURL to poll for results; null when no polling link is provided.

Document data#

Document data: types and allowed values
FieldJSON typeDescription and allowed values
extractionsArray<Extraction result> | nullOne result per extraction performed on the document.

Tax entry#

Tax entry: types and allowed values
FieldJSON typeDescription and allowed values
typestringTax type classification
Allowed values: vat, gst, pst, hst, qst, sales_tax, service_tax, consumption_tax, igst, cgst, sgst, purchase_tax, withholding_tax, excise, customs, other
namestring | nullDisplay name as shown on invoice (e.g., 'State Tax', 'TVA', 'MwSt')
ratenumber | nullTax rate as percentage (e.g., 17 for 17%)
amountnumber | nullTax amount in document currency
basenumber | nullTaxable base printed for this rate's summary row, when the document prints per-rate bases (multi-rate invoices)
coordinatesCoordinates | nullCoordinates of the tax amount on the document
confidencestring | nullConfidence level for this tax entry
Allowed values: high, medium, low

Invoice line item#

Invoice line item: types and allowed values
FieldJSON typeDescription and allowed values
lineNumberinteger | nullLine item number or position
descriptionstring | nullProduct or service description
itemCodestring | nullInternal SKU, product code, or item number assigned by the seller
barcodestring | nullEAN, UPC, or other standardized barcode number (typically 8-14 digits)
quantitynumber | nullQuantity ordered
unitOfMeasurestring | nullUnit the quantity is counted in, normalized to a closed roster (UNIT, BOX, CARTON, PACK, PAIR, DOZEN, MG, G, KG, TON, LB, OZ, ML, CL, L, GAL, MM, CM, M, M2, M3, HOUR, DAY, MONTH). UNIT for plain item counts; null when the document does not indicate a unit and the quantity is not a plain count.
Allowed values: UNIT, BOX, CARTON, PACK, PAIR, DOZEN, MG, G, KG, TON, LB, OZ, ML, CL, L, GAL, MM, CM, M, M2, M3, HOUR, DAY, MONTH
unitSizenumber | nullMeasurable content of ONE unit when printed on the document (e.g. 0.5 for a '12 × 0.5L' line). Always paired with unitSizeUom. Extracted only — never derived. Informational: does NOT contribute to lineTotal math.
unitSizeUomstring | nullUnit of unitSize, normalized to a closed roster of MEASURE units only (MG, G, KG, TON, LB, OZ, ML, CL, L, GAL, MM, CM, M, M2, M3) — a subset of the unitOfMeasure roster: counts (UNIT, BOX, CARTON, ...) and time units are never a content size. Always paired with unitSize.
Allowed values: MG, G, KG, TON, LB, OZ, ML, CL, L, GAL, MM, CM, M, M2, M3
listPricenumber | nullGross/catalog unit price BEFORE discount (e.g., LIST PRICE, MSRP, מחיר מחירון). Documentation-only — does NOT contribute to line_total math. Populated only when the invoice prints a dedicated column for it; null otherwise.
unitPricenumber | nullNET price per unit (after discount). Used by line_total math.
discountAmountnumber | nullDiscount applied to line
discountPercentagenumber | nullDiscount percentage
taxAmountnumber | nullTax amount for this line
taxRatenumber | nullTax rate percentage
packagingAmountnumber | nullAdditive packaging charge (e.g., סה"כ ערך אריזה). Contributes to line_total like tax_amount does.
depositAmountnumber | nullAdditive deposit charge (e.g., סה"כ ערך פיקדון, container/bottle deposit). Contributes to line_total like tax_amount does.
unitsPerPackageinteger | nullStructural pack size — the whole-number count of singles that fit in one package/case (e.g., 24 cans per case, 6 bottles per pack). Never a volume or weight. Informational only — does NOT contribute to line_total. Null when the invoice has no dedicated pack-size column.
packageQuantitynumber | nullPer-line order quantity expressed in package units (e.g., number of cartons ordered). May be fractional (e.g., 0.2 of a carton). Informational only — does NOT contribute to line_total. Relationship when both are present: quantity ≈ package_quantity × units_per_package.
lineTotalnumber | nullTotal amount for this line
confidencestring | nullCategorical confidence, not a numeric probability. Null when unavailable.
Allowed values: high, medium, low
confidence_reasonsArray<string>Machine-readable reasons for the assigned confidence; an empty array means no reasons supplied.
Allowed values: no_field_evaluations, critical_fields_low, essential_fields_low, high_low_field_ratio, math_validation_failed, math_validation_passed, high_field_confidence, mixed_field_confidence

Coordinates#

Coordinates: types and allowed values
FieldJSON typeDescription and allowed values
pixelsArray<[integer, integer]>Exactly four [x, y] integer pairs on the normalized document image.
relativeArray<[number, number]>Exactly four [x, y] number pairs relative to image width and height.

Extraction result#

Extraction result: types and allowed values
FieldJSON typeDescription and allowed values
statusstringProcessing outcome. See the status meanings in Interpretation and calculations.
Allowed values: pending, in_process, failed, success, partial, empty
metaExtraction metadataIdentifiers and processing metadata for this response.
valuesMap<string, JSON value> | nullExtracted payload. Use the section matching meta.extractionType; null while unavailable, failed, or purged.
verifiedValuesMap<string, JSON value> | nullReviewer-finalized payload, with the same shape as values. Null before verification or after purge.
verifiedDiffArray<Verified difference> | nullReviewer changes. Null means not verified; an empty array means verified with no changes.
errorsArray<Map<string, JSON value>>Error objects supplied by the API. Properties vary by error; may include error_code, description, detail, and status_code.

Extraction metadata#

Extraction metadata: types and allowed values
FieldJSON typeDescription and allowed values
extractionIdstring (UUID) | nullIdentifier of this extraction.
modelTypestringModel used for this extraction. Metadata can include legacy model names.
Allowed values: gemina, google, openai, velox, praetorian, invictus
thinkingbooleanWhether thinking was enabled for this extraction.
correctionbooleanWhether correction was enabled for this extraction.
evaluationbooleanWhether evaluation was enabled for this extraction.
includeCoordinatesbooleanWhether document coordinates were requested.
processorClassstringProcessor identifier reported by the backend.
extractionTypestringDocumented extraction types; selects the schema of values and verifiedValues.
Allowed values: ocr, invoice_headers, invoice_line_items, custom_template, filetag
latencySecondsnumber | nullExtraction processing duration in seconds.
numberOfFieldsinteger | nullNumber of fields reported for the extraction.
purgeAtstring (date-time) | nullScheduled purge time, or null when not scheduled.
purgedAtstring (date-time) | nullActual purge time, or null before purge.
purgeReasonstring | nullReason for purging, or null when not purged.
Allowed values: user_deleted, retention_expired, admin_action
validationFeedbackValidation feedback | nullSchema for submitting reviewer edits, when available.
validatedbooleanWhether a reviewer validation has been stored.

Verified difference#

Verified difference: types and allowed values
FieldJSON typeDescription and allowed values
fieldstringLabel identifying the field changed by the reviewer.
statusstringWhether the reviewer corrected, added, or removed this field.
Allowed values: corrected, added, removed
pointerstring | nullJSON pointer locating the value. For a verified difference, null when the field was removed.
originalstring | integer | number | boolean | Array<JSON value> | Map<string, JSON value> | nullOriginal extracted value, preserving its JSON type.
verifiedstring | integer | number | boolean | Array<JSON value> | Map<string, JSON value> | nullReviewer-finalized value, preserving its JSON type.

Validation feedback#

Validation feedback: types and allowed values
FieldJSON typeDescription and allowed values
validationSchemaArray<string>Opaque validation keys in label:…|ptr:/… format; submit the keys unchanged.
validationFieldsArray<Validation field>Types and allowed values for editable fields not already described by a table column.
rowMutableTablesArray<Editable table>Tables whose rows reviewers may add or remove; columns describe each cell type.

Validation field#

Validation field: types and allowed values
FieldJSON typeDescription and allowed values
keystringOpaque validation key; matches an entry in validationSchema.
labelstringLabel identifying the editable field.
typestringValue type accepted for this editable field or column.
Allowed values: string, number, integer, boolean, date
enumArray<string> | nullAllowed string values, when a closed set is supplied; null means no enum constraint.
formatstring | nullOptional format hint (for example date). Null means no format hint.
descriptionstring | nullDescription.

Editable table#

Editable table: types and allowed values
FieldJSON typeDescription and allowed values
pointerstringJSON pointer locating the editable table within the extraction payload.
keyTemplatestringTemplate for validation keys in this table.
columnsArray<Editable column>Column definitions shared by every row in this editable table.

Editable column#

Editable column: types and allowed values
FieldJSON typeDescription and allowed values
namestringName.
typestringValue type accepted for this editable field or column.
Allowed values: string, number, integer, boolean, date
enumArray<string> | nullAllowed string values, when a closed set is supplied; null means no enum constraint.
formatstring | nullOptional format hint (for example date). Null means no format hint.
descriptionstring | nullDescription.

Interpretation and calculations#

Invoice header reconciliation: subtotalAmount + sum(taxes[].amount) ≈ totalAmount. subtotalAmount is the tax base after header discounts and rounding. Header discountAmount and roundingAmount retain the printed sign. A discountPercentage of 3 means 3%.

Invoice line math: lineTotal ≈ quantity × unitPrice + taxAmount + packagingAmount + depositAmount (when present). unitPrice is already net of line discounts: do not subtract discountAmount or discountPercentage again. listPrice is the printed gross/catalog price and does not enter this calculation.

Pack and content sizes are informational: quantity ≈ packageQuantity × unitsPerPackage when both are present. packageQuantity can be fractional; unitsPerPackage is an integer count. unitSize and unitSizeUom describe the printed measurable content of one unit, are never derived, and are both null if either is missing. Unsupported measurement units normalize to null.

Invoice header currency is an ISO 4217 string, not a closed backend enum. invoiceDate and dueDate are strings using ISO dates when possible.

Statuses: pending means queued; in_process means processing; success means completed; partial means only part of the work succeeded; failed means an error occurred; empty means no result. Check the status of each extraction as well as the response envelope.

API Reference#

Official SDKs

  • @gemina/sdk - TypeScript / Node.js (npm)
  • gemina - Python (PyPI)
  • Gemina.Sdk - C# (NuGet)
  • co.gemina:gemina-sdk - Java (Maven)
  • gemina/sdk - PHP (Packagist)
  • @gemina/elements - React chat UI (npm)

Extraction Types

Model Types

  • velox - Fast processing
  • praetorian - Balanced accuracy
  • invictus - Highest accuracy

Endpoints

  • POST /api/v1/documents/uploads
  • POST /api/v1/documents/uploads/web
  • GET /api/v1/documents/{id}
  • GET /api/v1/documents/results/{id}
  • GET /api/v1/documents/extractions/{id}
  • PUT /api/v1/documents/extractions/{id}/feedback

Response Statuses

  • success - Extraction completed
  • pending - Job queued
  • in_process - Processing
  • failed - Error occurred
  • partial - Part of the work succeeded
  • empty - No result

Document Intelligence

  • POST /api/v1/retrieval/query
  • POST /api/v1/retrieval/aggregate
  • POST /api/v1/chat/query
  • DELETE /api/v1/chat/sessions/{id}
  • GET /api/v1/chat/sessions
  • GET /api/v1/chat/sessions/{id}
  • DELETE /api/v1/chat/sessions/{id}/purge
  • POST /api/v1/sessions/token

Errors

  • Branch on errors[0].error_code
  • 401 - No credential / bad session token
  • 403 - Bad or revoked API key
  • 422 - Malformed request, or over the page limit
  • 429 - Back off on Retry-After
  • Error reference

MCP server

FileTag API

Ready to Get Started?

Sign up for a free trial and start extracting data from your documents today.