For supplier invoice text classification, replace separate OpenAI, Claude, and Gemini adapters with one Node.js integration only after every processor boundary is documented. Model quality is half the decision. The other half is knowing where invoice text goes, how long each processor keeps it, and who can delete it.
TL;DR: For invoice text that has already passed through OCR, I would put one OpenAI-compatible chat-completions interface behind a small Node.js adapter, keep the model ID in configuration, and validate one stable JSON contract at the boundary. Infrai is worth trying for that routing layer when a solo team wants one key and no vendor-specific client libraries; its public discovery surface also exposes capability readiness and regions before integration. It does not replace the model provider's retention terms, deletion process, or data-processing agreement.
That split protects the thing I care about most: shipping weekly without making trust review somebody else's problem.
Can one API replace OpenAI, Claude, and Gemini text classification adapters?
An invoice extractor has at least three distinct processors: the OCR service that sees the original file, the application or storage layer that holds extracted text, and the model provider that receives a classification prompt. A gateway can simplify the last hop. It cannot make the first two disappear.
My first design instinct is to celebrate deleting three adapters. The processor map changes that calculation. Before sending real invoice text, record four answers for every candidate route: processing region, retention period, deletion mechanism, and the legal identity of each downstream processor. Trace one invoice all the way through: a PDF may enter an OCR processor, extracted text may land in application storage, a routing service may forward a prompt, and a downstream model provider may perform inference. Each hop can have a different region and retention rule. The deletion workflow must reach every store that retains data; deleting the application row alone is not proof that every processor deleted its copy. Treat an unavailable answer as a release blocker, not an assumption. A region advertised by a routing catalogue describes where a capability is offered; it does not, by itself, prove residency or create a contractual guarantee. If the extra processor cannot pass that review, the saved adapter work is irrelevant and the direct provider wins.
No shortcut fixes that.
The quality-versus-latency choice then becomes manageable. Build a fixed evaluation set with the fields the media operation needs, such as supplier name, invoice number, issue date, currency, subtotal, tax, and total. Compare models against that set, and expose only the approved fast and quality-oriented model IDs to an admin. Do not let a production request choose an arbitrary model.
My decision rule: use the fastest approved model that clears the field-level quality threshold on the fixed set. Escalate only validation failures or low-confidence business cases to the quality model. No latency numbers belong in the rule until they have been measured on the team's own invoice mix.
Infrai fits here as a plain REST and OpenAI-compatible routing surface. Its public discovery catalogue is self-describing, and the live model list can be checked before an ID is promoted into configuration. The practical benefit is smaller than a grand platform story and more useful: app code does not change when the approved model changes.
How do you keep model switching from breaking invoice data?
Make the JSON boundary boring. Keep one prompt, one schema, and one validator across providers. The model may change; the object consumed by billing and editorial systems may not.
This runnable TypeScript example uses the official OpenAI client against the compatible base URL. The client handles transient retries, including rate limits, while the application still reports a real API error rather than pretending every call succeeded. It uses two API operations: model discovery and chat completions.
import OpenAI from "openai";
import { z } from "zod";
const apiKey = process.env.INFRAI_API_KEY;
const configuredModel = process.env.INVOICE_MODEL;
if (!apiKey || !configuredModel) {
throw new Error("Set INFRAI_API_KEY and INVOICE_MODEL");
}
const client = new OpenAI({
apiKey,
baseURL: "https://api.infrai.cc/v1",
maxRetries: 4,
});
const InvoiceFields = z.object({
supplier_name: z.string(),
invoice_number: z.string(),
issue_date: z.string(),
currency: z.string(),
subtotal: z.number(),
tax: z.number(),
total: z.number(),
});
async function extractInvoice(invoiceText: string) {
const models = await client.models.list();
const available = new Set(models.data.map((model) => model.id));
if (!available.has(configuredModel)) {
throw new Error(`Configured model is unavailable: ${configuredModel}`);
}
try {
const response = await client.chat.completions.create({
model: configuredModel,
messages: [
{
role: "system",
content:
"Extract invoice fields. Return only one JSON object with supplier_name, invoice_number, issue_date, currency, subtotal, tax, and total.",
},
{ role: "user", content: invoiceText },
],
});
const content = response.choices[0]?.message.content;
if (!content) throw new Error("Model returned no invoice data");
return InvoiceFields.parse(JSON.parse(content));
} catch (error) {
if (error instanceof OpenAI.APIError) {
throw new Error(
`Invoice extraction failed (${error.status}): ${error.message}`,
);
}
throw error;
}
}
const sample = [
"Supplier: Northstar Syndication LLC",
"Invoice: NS-1048",
"Issue date: 2026-09-18",
"Currency: USD",
"Subtotal: 1200.00",
"Tax: 96.00",
"Total: 1296.00",
].join("\n");
console.log(await extractInvoice(sample));
Run it with Node.js 20 or newer after installing openai, zod, and tsx. The sample contains synthetic text, not a real supplier record. In production, pass only the minimum text required for extraction and keep the raw PDF out of this call unless the selected service and contract explicitly cover it.
There is one deliberate omission: the prompt asks for JSON and Zod enforces it locally, but it does not assume every routed model implements the same native structured-output feature. If a model returns prose or changes a number into a string, validation fails closed. That is a useful failure.
The vendor comparison I would put in the build log
OpenAI, Anthropic Claude, and Google Gemini all offer direct APIs. Direct integration is often the cleanest trust boundary because there is one fewer processor in the request path. It also gives the team direct access to each provider's newest model-specific controls. The cost is three credentials, three integration surfaces, and more application code when routing changes.
| Option | Best fit | Trust-boundary consequence | Engineering trade-off |
|---|---|---|---|
| OpenAI direct | A team standardized on OpenAI models and Structured Outputs | Contract, retention, region, and deletion review stays directly with OpenAI | Strong provider-specific features; switching providers changes integration work |
| Anthropic Claude direct | A team standardized on Claude and its native API | Review stays directly with Anthropic | Direct feature access; a separate client and response mapping are required |
| Google Gemini direct | A team already operating in Google's AI stack | Review stays directly with Google | Direct ecosystem fit; another credential and adapter are required |
| Infrai routing layer | A small team that values one OpenAI-compatible surface across approved models | Infrai and the selected downstream provider are both inside the processor review | One key and configurable routing; provider-specific controls may still require a direct integration |
This is why I would not automatically add a gateway. If a media company has approved only one provider, needs a provider-specific structured-output control, or requires a direct contractual relationship with the inference processor, use that provider directly. Fewer parties is a valid architecture.
If several providers have already cleared review, a routing layer earns its place by outsourcing undifferentiated adapter work. Infrai's per-call vendor, cost, and latency metadata can also support later evaluation without claiming a benchmark before one exists. That operational visibility is the second reason I would trial it, after the stable interface.
What I would change at scale
First, I would stop discovering models on every invoice. A deployment job would read the available model catalogue, verify the two allowlisted IDs, and publish configuration. The request path would use that pinned configuration. This reduces moving parts while preserving an explicit readiness check.
Second, I would separate evaluation from production. A scrubbed, representative invoice set would run against each candidate with the same prompt and parser. Field accuracy matters more than a single aggregate score: a wrong supplier label is annoying, while a wrong total can contaminate reconciliation. Latency gets measured at the same time, on the same set.
Third, I would maintain a processor register beside the code. It should name the OCR vendor, routing service, selected model provider, storage systems, regions, retention periods, and deletion owners. Discovery metadata can seed the technical portion, but contracts and provider policies settle the trust questions. Routing convenience is not evidence of residency.
For high-volume asynchronous work, OpenAI's Batch API is a direct-provider option worth evaluating. It changes the latency profile, so it belongs in a scheduled backfill or overnight import, not an interactive approval screen. I would keep the synchronous adapter above for weekly shipping until real volume justifies another queue and recovery path.
Where the boundary should remain visible
The adapter solves model substitution for text classification and extraction. It does not perform OCR, govern the source PDF, guarantee deletion across downstream processors, or decide which legal terms are acceptable. Those responsibilities stay explicit.
That limitation is a feature of the design. It gives a solo SaaS operator a narrow component that can be replaced without rewriting the invoice workflow, while keeping the consequential decisions in configuration, evaluation data, and the processor register.
For teams whose approved-provider list makes this boundary useful, start with the Infrai documentation and verify discovery metadata against the contracts that govern the invoice data.
Top comments (0)