API Documentation
Everything you need to integrate Gemina's powerful document extraction into your application.
Getting Started
Extract structured data from documents programmatically. There are three ways in: an official SDK (recommended — install it and process a document in a single call, then search and chat over your results), raw HTTP against the REST API from any language, or a drop-in chat UI you embed in your own app. Responses are structured JSON with extracted fields, coordinates, and confidence scores.
https://api.gemina.coX-API-Key: your-api-keyOfficial SDKs
RecommendedInstall 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/sdkProcess 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);Search & analyze your documents
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:
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)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:
// 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" },
});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;
}
}Install
Add the official Gemina SDK to your project:
pip install geminaProcess 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 asyncio
from gemina import GeminaClient, ExtractionTypeModel
async def main():
async with GeminaClient("YOUR_API_KEY") as client:
result = await client.process_document(
"invoice.png", # path, bytes, or a binary file object
[ExtractionTypeModel.INVOICE_HEADERS],
)
values = result.data.extractions[0].values
print("Supplier:", values["vendorName"]["value"])
print("Total: ", values["totalAmount"]["value"], values["currency"]["value"])
print("Date: ", values["invoiceDate"]["value"])
asyncio.run(main())Search & analyze your documents
Query everything you've processed with structured filters, semantic search, or both — and compute exact, database-backed aggregations without re-reading a document:
from gemina import GeminaClient, RetrievalQueryInDTO
from gemina.generated.models.retrieval_filters_dto import RetrievalFiltersDTO
async def search():
async with GeminaClient("YOUR_API_KEY") as client:
page = await client.retrieval.retrieval_query(RetrievalQueryInDTO(
mode="hybrid", # structured | semantic | hybrid
text="cleaning services",
filters=RetrievalFiltersDTO(total_amount_min=100),
top_k=5,
))
for item in page.items:
print(item.vendor_name, item.total_amount, item.currency,
item.issue_date, item.document_id)
from gemina import GeminaClient, RetrievalAggregateInDTO
from gemina.generated.models.aggregate_metric_dto import AggregateMetricDTO
async def totals_by_vendor():
async with GeminaClient("YOUR_API_KEY") as client:
report = await client.retrieval.retrieval_aggregate(RetrievalAggregateInDTO(
metrics=[
AggregateMetricDTO(op="sum", field="total_amount"),
AggregateMetricDTO(op="count"),
],
group_by=["vendor_name"],
))
for row in report.rows:
print(row.group, row.values["sum_total_amount"].actual_instance,
row.values["count"].actual_instance)Chat with your documents
Ask in natural language and get grounded answers with citations. Follow-up questions keep their conversation context:
from gemina import GeminaClient, ChatQueryInDTO
async def ask():
async with GeminaClient("YOUR_API_KEY") as client:
reply = await client.chat.chat_query(ChatQueryInDTO(
message="What is the total amount of my invoices from last month?",
))
print(reply.answer)
print("confident:", reply.confident)
print("citations:", reply.citations)
async def conversation():
async with GeminaClient("YOUR_API_KEY") as client:
chat = client.conversation()
await chat.send("How much did we spend on cleaning in 2020?")
follow = await chat.send("And which vendor was most expensive?") # remembers 2020 / cleaning
print(follow.answer, "· session:", chat.session_id)
await chat.delete() # end it server-side (or chat.reset() to just forget it locally)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:
from gemina import GeminaClient, SessionTokenInDTO
async def mint_token():
async with GeminaClient("YOUR_API_KEY") as client: # server-side only
token = await client.sessions.mint_retrieval_token(SessionTokenInDTO(
end_user_id="customer-42", # omit for a whole-account session
ttl_seconds=600, # clamped server-side to [300, 900]
))
return token.token # ship this to the frontendError handling
Typed errors separate a terminal processing failure from a still-processing timeout you can resume:
import asyncio
from gemina import GeminaClient, GeminaProcessingError, GeminaTimeoutError
from gemina import ExtractionTypeModel
async def main():
async with GeminaClient("YOUR_API_KEY") as client:
try:
result = await client.process_document(
"invoice.png", [ExtractionTypeModel.INVOICE_HEADERS],
)
except GeminaProcessingError as err: # terminal "failed" status
print("processing failed:", err.result.errors)
except GeminaTimeoutError as err: # still processing at deadline
print("timed out; resume from:", err.last_result)
asyncio.run(main())Install
Add the official Gemina SDK to your project:
dotnet add package Gemina.SdkProcess 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:
using Gemina.Sdk;
using Gemina.Sdk.Model;
using Newtonsoft.Json.Linq;
var client = new GeminaClient("YOUR_API_KEY");
var result = await client.ProcessDocumentAsync(
GeminaDocumentSource.FromFile("invoice.png"),
new List<ExtractionTypeModel> { ExtractionTypeModel.InvoiceHeaders });
var headers = result.Data.Extractions[0];
Console.WriteLine($"Status: {result.Status}");
Console.WriteLine($"Supplier: {(headers.Values["vendorName"] as JObject)?["value"]}");
Console.WriteLine($"Total: {(headers.Values["totalAmount"] as JObject)?["value"]}");
Console.WriteLine($"Date: {(headers.Values["invoiceDate"] as JObject)?["value"]}");Search & analyze your documents
Query everything you've processed with structured filters, semantic search, or both — and compute exact, database-backed aggregations without re-reading a document:
using Gemina.Sdk.Model;
var query = await client.Retrieval.RetrievalQueryAsync(new RetrievalQueryInDTO(
text: "cleaning services invoices",
topK: 5));
foreach (var item in query.Items)
{
Console.WriteLine($"{item.VendorName} — {item.TotalAmount} {item.Currency} " +
$"(issued {item.IssueDate:d}, document {item.DocumentId})");
}
var aggregate = await client.Retrieval.RetrievalAggregateAsync(new RetrievalAggregateInDTO(
metrics: new List<AggregateMetricDTO>
{
new AggregateMetricDTO(AggregateMetricDTO.FieldEnum.TotalAmount, AggregateMetricDTO.OpEnum.Sum),
},
groupBy: new List<RetrievalAggregateInDTO.GroupByEnum>
{
RetrievalAggregateInDTO.GroupByEnum.VendorName,
}));
foreach (var row in aggregate.Rows)
{
Console.WriteLine($"{row.Group["vendor_name"]}: {row.Values["sum_total_amount"].ActualInstance}");
}Chat with your documents
Ask in natural language and get grounded answers with citations. Follow-up questions keep their conversation context:
var chat = await client.Chat.ChatQueryAsync(new ChatQueryInDTO(
message: "How much did I spend on cleaning services this year?"));
Console.WriteLine(chat.Answer);
Console.WriteLine($"Confident: {chat.Confident}");
Console.WriteLine($"Citations: {string.Join(", ", chat.Citations ?? new List<string>())}");
var chat = client.Conversation();
await chat.SendAsync("How much did we spend on cleaning in 2020?");
var follow = await chat.SendAsync("And which vendor was most expensive?"); // remembers 2020 / cleaning
Console.WriteLine($"{follow.Answer} · session: {chat.SessionId}");
await chat.DeleteAsync(); // end it server-side (or chat.Reset() to just forget it locally)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:
var session = await client.Sessions.MintRetrievalTokenAsync(new SessionTokenInDTO(
endUserId: "user-123",
ttlSeconds: 900));
// Send session.Token to your frontend; it expires in session.ExpiresIn seconds.
var sessionClient = GeminaClient.WithSessionToken(session.Token);Error handling
Typed errors separate a terminal processing failure from a still-processing timeout you can resume:
using Gemina.Sdk.Client;
try
{
var result = await client.ProcessDocumentAsync(source, extractionTypes);
}
catch (GeminaProcessingException ex)
{
Console.WriteLine($"Processing failed: {ex.Result.Errors?.Count} error(s)");
}
catch (GeminaTimeoutException ex)
{
// Resume polling on your own schedule:
var result = await client.GetProcessingResultAsync(ex.CorrelationId);
}
catch (ApiException ex)
{
Console.WriteLine($"HTTP {ex.ErrorCode}: {ex.Message}");
}Install
Add the official Gemina SDK to your project:
<dependency>
<groupId>co.gemina</groupId>
<artifactId>gemina-sdk</artifactId>
<version>0.2.1</version>
</dependency>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 java.io.File;
import java.util.Collections;
import java.util.Map;
import co.gemina.sdk.GeminaClient;
import co.gemina.sdk.GeminaDocumentSource;
import co.gemina.sdk.generated.model.DocumentProcessingResultOutDTO;
import co.gemina.sdk.generated.model.ExtractionTypeModel;
public class Quickstart {
public static void main(String[] args) throws Exception {
GeminaClient client = new GeminaClient(System.getenv("GEMINA_API_KEY"));
DocumentProcessingResultOutDTO result = client.processDocument(
GeminaDocumentSource.fromFile(new File("invoice.pdf")),
Collections.singletonList(ExtractionTypeModel.INVOICE_HEADERS));
System.out.println("status: " + result.getStatus());
// Each value field is a map: {"value": ..., "confidence": ..., "coordinates": ...}
Map<String, Object> values = result.getData().getExtractions().get(0).getValues();
System.out.println("vendor: " + field(values, "vendorName"));
System.out.println("total: " + field(values, "totalAmount"));
System.out.println("date: " + field(values, "invoiceDate"));
}
@SuppressWarnings("unchecked")
static Object field(Map<String, Object> values, String name) {
Map<String, Object> f = (Map<String, Object>) values.get(name);
return f == null ? null : f.get("value");
}
}Search & analyze your documents
Query everything you've processed with structured filters, semantic search, or both — and compute exact, database-backed aggregations without re-reading a document:
import co.gemina.sdk.generated.model.*;
RetrievalQueryOutDTO hits = client.retrieval().retrievalQuery(
new RetrievalQueryInDTO()
.text("cloud hosting invoices over 500 euro")
.filters(new RetrievalFiltersDTO().currency("EUR"))
.limit(10));
for (QueryResultItemDTO item : hits.getItems()) {
System.out.println(item.getVendorName() + " " + item.getTotalAmount()
+ " " + item.getIssueDate() + " (document " + item.getDocumentId() + ")");
}
RetrievalAggregateOutDTO totals = client.retrieval().retrievalAggregate(
new RetrievalAggregateInDTO()
.metrics(Collections.singletonList(new AggregateMetricDTO()
.op(AggregateMetricDTO.OpEnum.SUM)
.field(AggregateMetricDTO.FieldEnum.TOTAL_AMOUNT)))
.groupBy(Collections.singletonList(RetrievalAggregateInDTO.GroupByEnum.VENDOR_NAME)));
for (AggregateRowDTO row : totals.getRows()) {
System.out.println(row.getGroup() + " -> " + row.getValues());
}Chat with your documents
Ask in natural language and get grounded answers with citations. Follow-up questions keep their conversation context:
ChatQueryOutDTO reply = client.chat().chatQuery(
new ChatQueryInDTO().message("How much did we spend on hosting last quarter?"));
System.out.println(reply.getAnswer());
System.out.println("confident: " + reply.getConfident());
System.out.println("citations: " + reply.getCitations());
GeminaClient.GeminaChatConversation chat = client.conversation();
chat.send("How much did we spend on cleaning in 2020?");
ChatQueryOutDTO follow = chat.send("And which vendor was most expensive?"); // remembers 2020 / cleaning
System.out.println(follow.getAnswer() + " · session: " + chat.getSessionId());
chat.delete(); // end it server-side (or chat.reset() to just forget it locally)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:
SessionTokenOutDTO session = client.sessions().mintRetrievalToken(
new SessionTokenInDTO()
.endUserId("user-123")
.ttlSeconds(900));
// Send session.getToken() to your frontend.
System.out.println(session.getToken() + " expires in " + session.getExpiresIn() + "s");Error handling
Typed errors separate a terminal processing failure from a still-processing timeout you can resume:
try {
DocumentProcessingResultOutDTO result = client.processDocument(source, types, options);
} catch (GeminaProcessingException e) {
System.err.println("processing failed: " + e.getResult().getErrors());
} catch (GeminaTimeoutException e) {
UUID correlationId = e.getCorrelationId(); // resume polling with this
DocumentProcessingResultOutDTO last = client.documents()
.getDocumentProcessingResultByCorrelationId(correlationId);
} catch (ApiException e) {
System.err.println("HTTP " + e.getCode() + ": " + e.getResponseBody());
}Install
Add the official Gemina SDK to your project:
composer require gemina/sdkProcess 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:
<?php
require 'vendor/autoload.php';
use Gemina\Sdk\GeminaClient;
$client = new GeminaClient(getenv('GEMINA_API_KEY'));
$result = $client->processDocument('invoice.png', ['invoice_headers']);
echo 'Status: ', $result->getStatus(), PHP_EOL;
$extraction = $result->getData()->getExtractions()[0];
$values = $extraction->getValues();
// Each field is an object with ->value (plus ->coordinates and ->confidence when available)
echo 'Supplier: ', $values['vendorName']->value ?? 'n/a', PHP_EOL;
echo 'Total: ', $values['totalAmount']->value ?? 'n/a', PHP_EOL;
echo 'Date: ', $values['invoiceDate']->value ?? 'n/a', PHP_EOL;Search & analyze your documents
Query everything you've processed with structured filters, semantic search, or both — and compute exact, database-backed aggregations without re-reading a document:
use Gemina\Sdk\Model\RetrievalQueryInDTO;
$out = $client->retrieval()->retrievalQuery(new RetrievalQueryInDTO([
'text' => 'cloud hosting invoices from June',
'top_k' => 10,
]));
foreach ($out->getItems() as $item) {
printf(
"%s | %s | %s %s\n",
$item->getDocumentId(),
$item->getVendorName(),
$item->getTotalAmount(),
$item->getCurrency(),
);
}
use Gemina\Sdk\Model\AggregateMetricDTO;
use Gemina\Sdk\Model\RetrievalAggregateInDTO;
$agg = $client->retrieval()->retrievalAggregate(new RetrievalAggregateInDTO([
'metrics' => [new AggregateMetricDTO(['op' => 'sum', 'field' => 'total_amount'])],
'group_by' => ['vendor_name'],
]));
foreach ($agg->getRows() as $row) {
print_r($row->getGroup());
print_r($row->getValues());
}Chat with your documents
Ask in natural language and get grounded answers with citations. Follow-up questions keep their conversation context:
use Gemina\Sdk\Model\ChatQueryInDTO;
$chat = $client->chat()->chatQuery(new ChatQueryInDTO([
'message' => 'How much did I spend on hosting in June, and with which vendor?',
]));
echo $chat->getAnswer(), PHP_EOL;
echo 'Confident: ', $chat->getConfident() ? 'yes' : 'no', PHP_EOL;
print_r($chat->getCitations());
$chat = $client->conversation();
$chat->send('How much did I spend on hosting in June, and with which vendor?');
$follow = $chat->send('And which month was cheapest?'); // remembers hosting / June
printf("%s · session: %s\n", $follow->getAnswer(), $chat->getSessionId());
$chat->delete(); // end it server-side (or $chat->reset() to just forget it locally)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:
use Gemina\Sdk\Model\SessionTokenInDTO;
$token = $client->sessions()->mintRetrievalToken(new SessionTokenInDTO([
'end_user_id' => 'user-42',
'ttl_seconds' => 900,
]));
echo $token->getToken(); // pass to the frontendError handling
Typed errors separate a terminal processing failure from a still-processing timeout you can resume:
use Gemina\Sdk\ApiException;
use Gemina\Sdk\GeminaProcessingException;
use Gemina\Sdk\GeminaTimeoutException;
try {
$result = $client->processDocument('invoice.png', ['invoice_headers']);
} catch (GeminaProcessingException $e) {
// Terminal "failed" — the full result is attached
print_r($e->getResult()->getErrors());
} catch (GeminaTimeoutException $e) {
echo 'Still processing: ', $e->getCorrelationId(), PHP_EOL;
} catch (ApiException $e) {
// Transport/HTTP errors from the generated client pass through unwrapped
echo $e->getCode(), ': ', $e->getResponseBody(), PHP_EOL;
}Raw HTTP API
No SDK, or on an unsupported language? Every endpoint is a plain REST call. Search, aggregate & chat 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.
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”, and “Session tokens” tabs above; the requests below are the underlying REST API.
Search your documents
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
}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).
Quick Start
Install the required package and set up your environment:
pip install requestsimport os
BASE_URL = os.getenv("GEMINA_BASE_URL", "https://api.gemina.co")
API_KEY = os.getenv("GEMINA_API_KEY", "")
HEADERS = {"X-API-Key": API_KEY}Authentication
All API requests require authentication via the X-API-Key header:
import requests
headers = {"X-API-Key": "your-api-key"}
response = requests.get(
"https://api.gemina.co/api/v1/documents/",
headers=headers
)Upload Document
Upload a document for extraction using multipart form data:
import requests
url = "https://api.gemina.co/api/v1/documents/uploads"
headers = {"X-API-Key": "your-api-key"}
form_data = [
("extraction_types", "invoice_headers"),
("extraction_types", "invoice_line_items"),
("external_id", "inv-2025-0001"),
("model_type", "invictus"),
]
files = {
"file": ("invoice.pdf", open("./invoice.pdf", "rb"), "application/pdf")
}
response = requests.post(url, headers=headers, data=form_data, files=files)
result = response.json()
print(result)Response Format
Successful extractions return structured JSON with field values and confidence scores:
{
"status": "success",
"meta": {
"documentId": "9860df92-64fe-4b53-9663-5c11b38a3051",
"externalId": "inv-2026-0001",
"filename": "invoice.pdf"
},
"data": {
"extractions": [
{
"extractionType": "invoice_headers",
"status": "success",
"values": {
"vendorName": {"value": "Acme Beverages Ltd.", "confidence": "high"},
"invoiceNumber": {"value": "IL-2026-04812", "confidence": "high"},
"invoiceDate": {"value": "2026-05-12", "confidence": "high"},
"currency": {"value": "ILS", "confidence": "high"},
"grossSubtotalAmount": {"value": 7663.63, "confidence": "high"},
"discountAmount": {"value": 229.91, "confidence": "high"},
"discountPercentage": {"value": 3.0, "confidence": "high"},
"roundingAmount": {"value": -0.18, "confidence": "high"},
"subtotalAmount": {"value": 7433.90, "confidence": "high"},
"taxes": [
{"type": "vat", "name": "VAT 18%", "rate": 18.0, "amount": 1338.10, "confidence": "high"}
],
"totalAmount": {"value": 8772.00, "confidence": "high"}
}
},
{
"extractionType": "invoice_line_items",
"status": "success",
"values": {
"line_items": [
{
"lineNumber": 1,
"description": "Premium 6-pack 330ml beer cans",
"itemCode": "BV-330-6",
"quantity": 12.0,
"listPrice": 65.00,
"unitPrice": 58.50,
"discountAmount": 6.50,
"discountPercentage": 10.0,
"taxRate": 18.0,
"packagingAmount": 0.30,
"depositAmount": 1.20,
"unitsPerPackage": 6,
"packageQuantity": 2.0,
"lineTotal": 703.50
},
{
"lineNumber": 2,
"description": "Olive oil 1L",
"itemCode": "OO-1L",
"quantity": 0.5,
"listPrice": null,
"unitPrice": 45.00,
"discountAmount": null,
"discountPercentage": null,
"taxRate": 18.0,
"packagingAmount": null,
"depositAmount": null,
"unitsPerPackage": null,
"packageQuantity": null,
"lineTotal": 22.50
}
],
"total_lines": 2
}
}
]
}
}Quick Start
Install the required packages:
npm install axios form-dataimport axios from "axios";
const BASE_URL = process.env.GEMINA_BASE_URL || "https://api.gemina.co";
const API_KEY = process.env.GEMINA_API_KEY || "";
const client = axios.create({
baseURL: BASE_URL,
headers: { "X-API-Key": API_KEY },
timeout: 90_000,
});Authentication
All API requests require authentication via the X-API-Key header:
import axios from "axios";
const client = axios.create({
baseURL: "https://api.gemina.co",
headers: { "X-API-Key": "your-api-key" },
});
const response = await client.get("/api/v1/documents/");Upload Document
Upload a document for extraction using form data:
import fs from "fs";
import FormData from "form-data";
import axios from "axios";
const form = new FormData();
form.append("extraction_types", "invoice_headers");
form.append("extraction_types", "invoice_line_items");
form.append("external_id", "inv-2025-0001");
form.append("model_type", "invictus");
form.append("file", fs.createReadStream("./invoice.pdf"));
const response = await axios.post(
"https://api.gemina.co/api/v1/documents/uploads",
form,
{
headers: {
"X-API-Key": "your-api-key",
...form.getHeaders(),
},
}
);
console.log(response.data);Response Format
Successful extractions return structured JSON with field values and confidence scores:
{
"status": "success",
"meta": {
"documentId": "9860df92-64fe-4b53-9663-5c11b38a3051",
"externalId": "inv-2026-0001",
"filename": "invoice.pdf"
},
"data": {
"extractions": [
{
"extractionType": "invoice_headers",
"status": "success",
"values": {
"vendorName": {"value": "Acme Beverages Ltd.", "confidence": "high"},
"invoiceNumber": {"value": "IL-2026-04812", "confidence": "high"},
"invoiceDate": {"value": "2026-05-12", "confidence": "high"},
"currency": {"value": "ILS", "confidence": "high"},
"grossSubtotalAmount": {"value": 7663.63, "confidence": "high"},
"discountAmount": {"value": 229.91, "confidence": "high"},
"discountPercentage": {"value": 3.0, "confidence": "high"},
"roundingAmount": {"value": -0.18, "confidence": "high"},
"subtotalAmount": {"value": 7433.90, "confidence": "high"},
"taxes": [
{"type": "vat", "name": "VAT 18%", "rate": 18.0, "amount": 1338.10, "confidence": "high"}
],
"totalAmount": {"value": 8772.00, "confidence": "high"}
}
},
{
"extractionType": "invoice_line_items",
"status": "success",
"values": {
"line_items": [
{
"lineNumber": 1,
"description": "Premium 6-pack 330ml beer cans",
"itemCode": "BV-330-6",
"quantity": 12.0,
"listPrice": 65.00,
"unitPrice": 58.50,
"discountAmount": 6.50,
"discountPercentage": 10.0,
"taxRate": 18.0,
"packagingAmount": 0.30,
"depositAmount": 1.20,
"unitsPerPackage": 6,
"packageQuantity": 2.0,
"lineTotal": 703.50
},
{
"lineNumber": 2,
"description": "Olive oil 1L",
"itemCode": "OO-1L",
"quantity": 0.5,
"listPrice": null,
"unitPrice": 45.00,
"discountAmount": null,
"discountPercentage": null,
"taxRate": 18.0,
"packagingAmount": null,
"depositAmount": null,
"unitsPerPackage": null,
"packageQuantity": null,
"lineTotal": 22.50
}
],
"total_lines": 2
}
}
]
}
}Quick Start
Add OkHttp to your Maven or Gradle project:
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp</artifactId>
<version>4.12.0</version>
</dependency>String BASE_URL = System.getenv("GEMINA_BASE_URL") != null
? System.getenv("GEMINA_BASE_URL") : "https://api.gemina.co";
String API_KEY = System.getenv("GEMINA_API_KEY");
OkHttpClient client = new OkHttpClient.Builder()
.connectTimeout(90, TimeUnit.SECONDS)
.readTimeout(90, TimeUnit.SECONDS)
.build();Authentication
All API requests require authentication via the X-API-Key header:
Request request = new Request.Builder()
.url("https://api.gemina.co/api/v1/documents/")
.header("X-API-Key", "your-api-key")
.get()
.build();
Response response = client.newCall(request).execute();Upload Document
Upload a document for extraction using multipart form data:
File invoiceFile = new File("invoice.pdf");
RequestBody requestBody = new MultipartBody.Builder()
.setType(MultipartBody.FORM)
.addFormDataPart("extraction_types", "invoice_headers")
.addFormDataPart("extraction_types", "invoice_line_items")
.addFormDataPart("external_id", "inv-2025-0001")
.addFormDataPart("model_type", "invictus")
.addFormDataPart("file", invoiceFile.getName(),
RequestBody.create(invoiceFile, MediaType.parse("application/pdf")))
.build();
Request request = new Request.Builder()
.url("https://api.gemina.co/api/v1/documents/uploads")
.header("X-API-Key", "your-api-key")
.post(requestBody)
.build();
Response response = client.newCall(request).execute();
System.out.println(response.body().string());Response Format
Successful extractions return structured JSON with field values and confidence scores:
{
"status": "success",
"meta": {
"documentId": "9860df92-64fe-4b53-9663-5c11b38a3051",
"externalId": "inv-2026-0001",
"filename": "invoice.pdf"
},
"data": {
"extractions": [
{
"extractionType": "invoice_headers",
"status": "success",
"values": {
"vendorName": {"value": "Acme Beverages Ltd.", "confidence": "high"},
"invoiceNumber": {"value": "IL-2026-04812", "confidence": "high"},
"invoiceDate": {"value": "2026-05-12", "confidence": "high"},
"currency": {"value": "ILS", "confidence": "high"},
"grossSubtotalAmount": {"value": 7663.63, "confidence": "high"},
"discountAmount": {"value": 229.91, "confidence": "high"},
"discountPercentage": {"value": 3.0, "confidence": "high"},
"roundingAmount": {"value": -0.18, "confidence": "high"},
"subtotalAmount": {"value": 7433.90, "confidence": "high"},
"taxes": [
{"type": "vat", "name": "VAT 18%", "rate": 18.0, "amount": 1338.10, "confidence": "high"}
],
"totalAmount": {"value": 8772.00, "confidence": "high"}
}
},
{
"extractionType": "invoice_line_items",
"status": "success",
"values": {
"line_items": [
{
"lineNumber": 1,
"description": "Premium 6-pack 330ml beer cans",
"itemCode": "BV-330-6",
"quantity": 12.0,
"listPrice": 65.00,
"unitPrice": 58.50,
"discountAmount": 6.50,
"discountPercentage": 10.0,
"taxRate": 18.0,
"packagingAmount": 0.30,
"depositAmount": 1.20,
"unitsPerPackage": 6,
"packageQuantity": 2.0,
"lineTotal": 703.50
},
{
"lineNumber": 2,
"description": "Olive oil 1L",
"itemCode": "OO-1L",
"quantity": 0.5,
"listPrice": null,
"unitPrice": 45.00,
"discountAmount": null,
"discountPercentage": null,
"taxRate": 18.0,
"packagingAmount": null,
"depositAmount": null,
"unitsPerPackage": null,
"packageQuantity": null,
"lineTotal": 22.50
}
],
"total_lines": 2
}
}
]
}
}Quick Start
Create an HttpClient instance for API requests:
using System.Net.Http;
var baseUrl = Environment.GetEnvironmentVariable("GEMINA_BASE_URL")
?? "https://api.gemina.co";
var apiKey = Environment.GetEnvironmentVariable("GEMINA_API_KEY") ?? "";
var client = new HttpClient
{
BaseAddress = new Uri(baseUrl),
Timeout = TimeSpan.FromSeconds(90)
};
client.DefaultRequestHeaders.Add("X-API-Key", apiKey);Authentication
All API requests require authentication via the X-API-Key header:
using var client = new HttpClient();
client.DefaultRequestHeaders.Add("X-API-Key", "your-api-key");
var response = await client.GetAsync(
"https://api.gemina.co/api/v1/documents/"
);Upload Document
Upload a document for extraction using multipart form data:
using var form = new MultipartFormDataContent();
form.Add(new StringContent("invoice_headers"), "extraction_types");
form.Add(new StringContent("invoice_line_items"), "extraction_types");
form.Add(new StringContent("inv-2025-0001"), "external_id");
form.Add(new StringContent("invictus"), "model_type");
var fileBytes = await File.ReadAllBytesAsync("invoice.pdf");
var fileContent = new ByteArrayContent(fileBytes);
fileContent.Headers.ContentType =
new MediaTypeHeaderValue("application/pdf");
form.Add(fileContent, "file", "invoice.pdf");
var response = await client.PostAsync(
"https://api.gemina.co/api/v1/documents/uploads",
form
);
var result = await response.Content.ReadAsStringAsync();
Console.WriteLine(result);Response Format
Successful extractions return structured JSON with field values and confidence scores:
{
"status": "success",
"meta": {
"documentId": "9860df92-64fe-4b53-9663-5c11b38a3051",
"externalId": "inv-2026-0001",
"filename": "invoice.pdf"
},
"data": {
"extractions": [
{
"extractionType": "invoice_headers",
"status": "success",
"values": {
"vendorName": {"value": "Acme Beverages Ltd.", "confidence": "high"},
"invoiceNumber": {"value": "IL-2026-04812", "confidence": "high"},
"invoiceDate": {"value": "2026-05-12", "confidence": "high"},
"currency": {"value": "ILS", "confidence": "high"},
"grossSubtotalAmount": {"value": 7663.63, "confidence": "high"},
"discountAmount": {"value": 229.91, "confidence": "high"},
"discountPercentage": {"value": 3.0, "confidence": "high"},
"roundingAmount": {"value": -0.18, "confidence": "high"},
"subtotalAmount": {"value": 7433.90, "confidence": "high"},
"taxes": [
{"type": "vat", "name": "VAT 18%", "rate": 18.0, "amount": 1338.10, "confidence": "high"}
],
"totalAmount": {"value": 8772.00, "confidence": "high"}
}
},
{
"extractionType": "invoice_line_items",
"status": "success",
"values": {
"line_items": [
{
"lineNumber": 1,
"description": "Premium 6-pack 330ml beer cans",
"itemCode": "BV-330-6",
"quantity": 12.0,
"listPrice": 65.00,
"unitPrice": 58.50,
"discountAmount": 6.50,
"discountPercentage": 10.0,
"taxRate": 18.0,
"packagingAmount": 0.30,
"depositAmount": 1.20,
"unitsPerPackage": 6,
"packageQuantity": 2.0,
"lineTotal": 703.50
},
{
"lineNumber": 2,
"description": "Olive oil 1L",
"itemCode": "OO-1L",
"quantity": 0.5,
"listPrice": null,
"unitPrice": 45.00,
"discountAmount": null,
"discountPercentage": null,
"taxRate": 18.0,
"packagingAmount": null,
"depositAmount": null,
"unitsPerPackage": null,
"packageQuantity": null,
"lineTotal": 22.50
}
],
"total_lines": 2
}
}
]
}
}Quick Start
Configure your API credentials:
<?php
define('GEMINA_BASE_URL', getenv('GEMINA_BASE_URL') ?: 'https://api.gemina.co');
define('GEMINA_API_KEY', getenv('GEMINA_API_KEY') ?: '');
if (empty(GEMINA_API_KEY)) {
throw new Exception('Set GEMINA_API_KEY environment variable');
}Authentication
All API requests require authentication via the X-API-Key header:
<?php
$ch = curl_init('https://api.gemina.co/api/v1/documents/');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'X-API-Key: your-api-key',
],
]);
$response = curl_exec($ch);
curl_close($ch);Upload Document
Upload a document for extraction using cURL:
<?php
$url = 'https://api.gemina.co/api/v1/documents/uploads';
$cfile = new CURLFile('./invoice.pdf', 'application/pdf', 'invoice.pdf');
$postData = [
'extraction_types[0]' => 'invoice_headers',
'extraction_types[1]' => 'invoice_line_items',
'external_id' => 'inv-2025-0001',
'model_type' => 'invictus',
'file' => $cfile,
];
$ch = curl_init($url);
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => $postData,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'X-API-Key: your-api-key',
],
]);
$response = curl_exec($ch);
curl_close($ch);
echo $response;Response Format
Successful extractions return structured JSON with field values and confidence scores:
{
"status": "success",
"meta": {
"documentId": "9860df92-64fe-4b53-9663-5c11b38a3051",
"externalId": "inv-2026-0001",
"filename": "invoice.pdf"
},
"data": {
"extractions": [
{
"extractionType": "invoice_headers",
"status": "success",
"values": {
"vendorName": {"value": "Acme Beverages Ltd.", "confidence": "high"},
"invoiceNumber": {"value": "IL-2026-04812", "confidence": "high"},
"invoiceDate": {"value": "2026-05-12", "confidence": "high"},
"currency": {"value": "ILS", "confidence": "high"},
"grossSubtotalAmount": {"value": 7663.63, "confidence": "high"},
"discountAmount": {"value": 229.91, "confidence": "high"},
"discountPercentage": {"value": 3.0, "confidence": "high"},
"roundingAmount": {"value": -0.18, "confidence": "high"},
"subtotalAmount": {"value": 7433.90, "confidence": "high"},
"taxes": [
{"type": "vat", "name": "VAT 18%", "rate": 18.0, "amount": 1338.10, "confidence": "high"}
],
"totalAmount": {"value": 8772.00, "confidence": "high"}
}
},
{
"extractionType": "invoice_line_items",
"status": "success",
"values": {
"line_items": [
{
"lineNumber": 1,
"description": "Premium 6-pack 330ml beer cans",
"itemCode": "BV-330-6",
"quantity": 12.0,
"listPrice": 65.00,
"unitPrice": 58.50,
"discountAmount": 6.50,
"discountPercentage": 10.0,
"taxRate": 18.0,
"packagingAmount": 0.30,
"depositAmount": 1.20,
"unitsPerPackage": 6,
"packageQuantity": 2.0,
"lineTotal": 703.50
},
{
"lineNumber": 2,
"description": "Olive oil 1L",
"itemCode": "OO-1L",
"quantity": 0.5,
"listPrice": null,
"unitPrice": 45.00,
"discountAmount": null,
"discountPercentage": null,
"taxRate": 18.0,
"packagingAmount": null,
"depositAmount": null,
"unitsPerPackage": null,
"packageQuantity": null,
"lineTotal": 22.50
}
],
"total_lines": 2
}
}
]
}
}Embed in your app
Ship a chat experience without building one. @gemina/elements is a drop-in React chat component (<GeminaChat>) with a security-hardened token manager — citations, low-confidence handling, and RTL support included, without ever exposing your API key to the browser.
Install
npm i @gemina/elements @gemina/sdk reactMint 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:
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}
onCitationClick={(documentId) => openDocumentViewer(documentId)}
/>;Response Fields
The response includes structured extraction values keyed by extraction type. Unpopulated fields are null.
invoice_headers fields
Each header field uses an envelope shape: { value, coordinates, confidence }. When the invoice doesn't print the value, the whole envelope is null — a defensive client can safely guard with if (response.discountAmount) { … }.
grossSubtotalAmount— Sum of line items before any header-level discount or rounding.discountAmount— Header-level discount in the document's currency. Sign is verbatim from the invoice: some templates print positives (229.91), others negatives (-229.91) or parenthesized values. Clients that subtract on their side must handle both.discountPercentage— Header-level discount as a percentage (e.g.3.0means 3%). Only populated when the invoice prints it.roundingAmount— Rounding adjustment (e.g. "round off", agorot rounding). Signed as printed; magnitude typically < 1.0 in document currency.subtotalAmount— The tax base: the value the invoice's VAT/tax percentage is calculated against, after any header-level discount and rounding, before tax.
The reconciliation identity (modulo printing artifacts):
subtotalAmount + Σ taxes[].amount ≈ totalAmountinvoice_line_items fields
Each item in the line_items array is a flat object (no envelope). Unpopulated fields are null.
listPrice— Gross/catalog unit price before any line-level discount. Populated only when the invoice prints a dedicated "list price" / "catalog price" / "MSRP" column. Documentation-only — do not use it in line-total math.unitPrice— NET price per unit, after any line-level discount. ThelineTotalmath uses this value, so the per-linediscountAmountanddiscountPercentageshould not be subtracted again. For the gross/catalog price, uselistPricewhen populated.packagingAmount— Additive packaging charge (crate fee, palletizing fee). Positive. Contributes tolineTotal.depositAmount— Additive deposit/refund charge (bottle deposit, container deposit). Positive. Contributes tolineTotal.unitsPerPackage— Structural pack size: whole-number count of units per package (e.g.24cans per case). Informational; never a volume or weight.packageQuantity— Order quantity in package units; may be fractional (e.g.2.1cartons). Informational. Most invoices print only one ofunitsPerPackageorpackageQuantity— both can benullindependently.
Line-total math contract:
lineTotal ≈ quantity × unitPrice
+ taxAmount (if present)
+ packagingAmount (if present)
+ depositAmount (if present)When both pack-size fields are present, quantity ≈ packageQuantity × unitsPerPackage — the relationship is approximate, not enforced.
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
invoice_headers- Invoice header fields (field list →)invoice_line_items- Line item details (field list →)ocr- Full text extractiondocument_details_hebrew- Hebrew documents
Model Types
velox- Fast processingpraetorian- Balanced accuracyinvictus- Highest accuracy
Endpoints
POST /api/v1/documents/uploadsPOST /api/v1/documents/uploads/webGET /api/v1/documents/{id}GET /api/v1/documents/results/{id}
Response Statuses
success- Extraction completedpending- Job queuedin_process- Processingfailed- Error occurred
Document Intelligence
POST /api/v1/retrieval/queryPOST /api/v1/retrieval/aggregatePOST /api/v1/chat/queryPOST /api/v1/sessions/token
FileTag API
- Document tagging via REST + MCP
- Free tier: 1,500 tags/month
- FileTag API Docs
Ready to Get Started?
Sign up for a free trial and start extracting data from your documents today.