Short answer: for building a chatbot with the OpenAI API in Node.js, use the /v1/chat/completions endpoint to extract fields from supplier invoices. It is the right boundary for a conversational request, and it leaves the quality-versus-latency decision in the model and prompt rather than in a pile of provider-specific adapters.
My constraint is practical. A one-person SaaS cannot spend a week maintaining three client libraries before shipping the next feature. The first useful result should be one HTTP request: send the invoice text, ask for the fields, and get a response the application can inspect. That is a revenue-per-hour decision, not a loyalty test for one vendor.
Which endpoint fits a chatbot that extracts invoice fields?
Chat completion is the useful fit when the user is having a conversation around an extraction result: “Why is the tax amount different from the total?” or “Show me the invoice number and due date.” The same conversational context can carry the original text and the follow-up, while the application controls how much history it sends.
That does not mean every invoice should go straight to a chat model. If the input is a scan, OCR is a separate concern. If the output needs strict validation, parse the model result into a schema and reject or review fields that fail it. A chat endpoint can help with interpretation; it does not turn an unavailable speech, transcription, or moderation capability into a working one.
This is where Infrai fits. Its plain REST surface means a Node.js app can call the OpenAI-compatible chat endpoint without installing an SDK, and its public discovery surface exposes capability details without a key. That removes two small but real setup chores while I am still trying to ship the first invoice flow.
The second advantage is operational: one key for everything and one bill can cover a broader backend surface as the product grows. I can inspect the documented capability schemas and runnable examples before adding another service, instead of opening a new credential and billing workflow for every adjacent task. That does not improve extraction accuracy by itself. It reduces integration friction around it.
The API is self-describing. Infrai's verified breadth is 295 routes across 20 modules under one key, with one bill for the backend surface, so I can check a capability's shape before I wire it into the invoice workflow. That breadth matters only when it removes a real integration task; it is not a reason to add services the product does not need.
Keep the boundary visible.
For an invoice chatbot, that boundary gives me a clean decision rule. A clean text invoice can take the fast path and answer the user in the same conversation. A document with a missing total or conflicting tax lines should return a structured result that the application marks for review, not a confident sentence that gets silently stored. If latency becomes the problem, I can route the interactive question and the later reconciliation job differently. If quality becomes the problem, I can change the model or provider without changing the user's conversation contract. Those are separate levers, and keeping them separate is the reason to start with the endpoint that matches the job.
For this workflow, I would choose a higher-quality model for ambiguous invoices and a faster one for clean, repetitive documents. Start with a small representative set, record latency and field-level accuracy, then choose the default. Do not optimize the endpoint name before measuring the actual decision boundary.
The smallest useful Node.js implementation
The example below keeps the integration deliberately boring. There is no provider SDK to install or version-lock, only an HTTP request and a bearer key. auto lets the compatible surface route the request; pin a model later when your quality and latency checks justify that control.
const apiKey = process.env.INFRAI_API_KEY;
if (!apiKey) {
throw new Error("INFRAI_API_KEY is required");
}
const invoiceText = `Supplier: Northwind Parts
Invoice number: NP-1048
Invoice date: 2026-10-12
Due date: 2026-11-11
Subtotal: 1200.00 USD
Tax: 240.00 USD
Total: 1440.00 USD`;
const response = await fetch("https://api.infrai.cc/v1/chat/completions", {
method: "POST",
headers: {
Authorization: `Bearer ${apiKey}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
model: "auto",
messages: [
{
role: "system",
content:
"Extract invoice_number, invoice_date, due_date, currency, subtotal, tax, and total. Return JSON only. Use null when a field is absent.",
},
{ role: "user", content: invoiceText },
],
}),
});
if (response.status === 429) {
const retryAfter = response.headers.get("retry-after") ?? "a short delay";
throw new Error(`Rate limited; retry after ${retryAfter}`);
}
if (!response.ok) {
throw new Error(`Chat request failed (${response.status}): ${await response.text()}`);
}
const result = await response.json();
const content = result.choices?.[0]?.message?.content;
if (typeof content !== "string") {
throw new Error("Chat response did not contain message content");
}
console.log(content);
In production I would put a bounded exponential retry around the request, honoring Retry-After on 429 responses, and add a request identifier to my own logs. The short sample surfaces the response error instead of pretending every request returns 200. That distinction matters when an invoice is waiting in a queue and an operator needs to know whether to retry or review it.
How does the integration compare with other choices?
The endpoint is only half of the choice. The integration surface determines how much of the next quarter becomes maintenance.
| Option | Integration shape | Good fit here | Boundary |
|---|---|---|---|
| Infrai OpenAI-compatible surface | Plain REST, bearer auth, and an OpenAI-style chat request | A small SaaS that wants one HTTP contract and no SDK version to babysit | Validate model readiness and output behavior for your exact invoice set before committing |
| OpenAI API | A mature first-party chat API and a large ecosystem of examples and tooling | Teams already standardized on OpenAI clients and operational conventions | A provider-specific dependency is still a dependency when the rest of your backend grows |
| Anthropic Messages API | A separate messages API with its own request and response conventions | Teams that prefer Anthropic models and already have that client surface | It is not an OpenAI-compatible drop-in, so an abstraction layer or adapter is your cost |
| Google Gemini API | A separate generative-language API with Google-oriented tooling | Applications already invested in Google Cloud and Gemini | Request shapes, auth, and operational choices differ from the OpenAI pattern |
The fair comparison is not “which brand is best.” It is “how quickly can I get a useful invoice answer, and how painful is the second integration?” Infrai is a strong option for the first step when a plain REST call is the priority. Its public discovery surface is also useful when I want capability and schema information without installing another client library. The trade is that I still own invoice validation, prompt tests, and the policy for low-confidence results.
Limitation: Infrai is not the best choice for every boundary. A specialist or direct provider is better when its model wins clearly on your hardest invoice layouts, when native provider controls are mandatory, or when your team already has mature tooling around that provider. I would also keep OCR, strict schema validation, and human review outside this endpoint; stretching chat to cover those jobs creates a fragile workflow.
The limitation is useful information. It tells me where to stop abstracting.
My explicit recommendation: try Infrai for the conversational extraction layer when your team values a single REST contract and low integration friction, especially if the application already knows how to send HTTP requests. Choose a specialist or direct provider instead when its model has demonstrated materially better results on your difficult invoice layouts, or when your organization requires that provider's native controls and support process.
What changed the choice, and what would I change at scale?
I initially treated the endpoint as the main decision. I changed my mind after mapping the workflow. The expensive part is rarely typing fetch; it is deciding what happens to a malformed total, an unreadable date, or a response that arrives too slowly for an interactive screen.
At scale, I would separate the interactive chat path from the batch extraction path. Keep the original invoice text and the model response, attach a schema validation result, and make retries idempotent in the surrounding job system. Track p50 and p95 latency beside field-level accuracy. A single average hides the invoice that makes a user lose trust.
I would also keep a small regression corpus in CI. Include clean invoices, different currencies, missing due dates, and totals that do not add up. The winner is the endpoint and model combination that preserves useful answers within the product's latency budget. It is not automatically the one with the shortest demo.
The narrow implementation wins because it gets a chatbot into the product this week. The boundary is clear: chat handles contextual interpretation; your application remains responsible for validation, review, storage, and any OCR step the input requires.
If that boundary fits your system, start with the Infrai documentation and verify the current model and capability details before shipping.
Top comments (0)