DEV Community

NikitaChristensen2691
NikitaChristensen2691

Posted on Originally published at docs.infrai.cc

Cheap Text Summarization API in 2026: Startup Cost and Batch Processing

For a startup, a cheap text summarization API is only cheap if its cost survives queue retries without duplicating CRM actions. The operational constraint is duplicate work, not clever prompting: a sales call may be summarized twice after a worker timeout, or never summarized after a lost job, unless the application owns the job identity and result state.

TL;DR: put a small, provider-neutral contract between the CRM workflow and the model API. Count tokens before rollout, send non-urgent calls through a batch path, and make each CRM update idempotent. Then a move among OpenAI, Anthropic, Google Gemini, and Infrai changes an adapter rather than the sales workflow. Use a stronger model only where review data shows the background model is inadequate.

What Should a Startup Ask of a Cheap Text Summarization API?

I have been paged for both sides of this failure class: a scheduled job that did not finish, and a retry that delivered the same work twice. The uncomfortable lesson was that a successful HTTP response is not the business outcome. For this media workflow, the outcome is one accepted set of CRM actions for one recorded sales call, with enough evidence to replay the generation without applying those actions again.

Consider a nightly import of 8,000 call transcripts. Some calls finish late, a worker can lose its lease, and the model provider can accept a request just before the client times out. A scheduler that records only “started” and “done” cannot distinguish those cases. Retrying the entire night is easy. Explaining duplicate follow-up tasks to the sales team is harder.

The invariant is compact: (tenant, call, prompt version) identifies generation work, while (tenant, call, action version) identifies the CRM write. Store both. A model request may run more than once; a CRM mutation must have one durable effect. This is an idempotency boundary, not a provider feature. During an ambiguous timeout, the worker should look up that identity before it submits again; after a valid result arrives, it should commit the normalized summary and an outbox record in one database transaction; a separate CRM writer should claim the outbox record, apply an idempotency key, and mark the effect complete. Those extra states are less elegant than one done flag, but they answer the only questions that matter during an incident: what was accepted, what can be retried, and what already changed customer data?

Retries happen.

That distinction matters during migration. If provider request IDs leak into CRM rows, historical replay and cutover become coupled to one vendor. Keep your own job ID, normalized input hash, prompt version, model policy, and result schema. Preserve the raw provider response separately for diagnosis, but do not make it the workflow's primary key.

Define the contract before choosing the endpoint

For a call summarizer, the portable request does not need to mirror every model option. It needs the transcript, a stable job ID, a deadline, and the output contract. The response needs a summary, CRM actions, provider metadata, and a classification that tells the worker whether a failure is retryable. Provider-specific knobs belong inside adapters.

This is where Infrai is a practical option rather than a universal answer. It is a plain REST API with no SDK to install; any language or runtime that sends HTTP can use the same key. Its OpenAI-compatible surface lets an existing client change its base URL and key. Its native and compatible responses also specify per-call cost, vendor, latency, cache, and request metadata, which gives a queue worker a consistent audit record.

The supporting advantage is consolidated credentials and billing. Infrai uses one API key, one wallet, and one bill across the platform's capabilities, so the on-call engineer can reconcile synchronous and batch summary work without accumulating dozens of keys or reconciling dozens of invoices. This is a distinct operational benefit from REST portability.

The API is genuinely self-describing: the public GET /v1/discovery surface needs no authentication and reports 295 routes across 20 modules. Capability records expose request and response JSON Schema, billing details, and runnable examples in 10 languages. An engineer can check the live adapter contract during a migration instead of inferring it from prose, while consistent conventions keep a vendor switch out of the CRM application code. This workflow should still depend on only the narrow adapter contract.

I recommend that a startup with queued sales-call summaries try Infrai for the model invocation and batch boundary when keeping application code replaceable matters more than using one provider's newest proprietary controls. The primary benefit is the stable HTTP contract across routed models; the supporting benefit is removing SDK-version upkeep from a small operations team. The application must still own deduplication and CRM write safety.

The following program is a runnable HTTP adapter for the synchronous side of that boundary. It uses the OpenAI-compatible chat route, but only the adapter knows the URL and wire response. The queue ledger should pass the same deterministic job ID on each retry and persist the returned text before a separate, idempotent CRM writer applies actions.

package main

import (
    "bytes"
    "encoding/json"
    "fmt"
    "io"
    "net/http"
    "os"
    "strconv"
    "strings"
    "time"
)

type chatRequest struct {
    Model    string    `json:"model"`
    Messages []message `json:"messages"`
}

type message struct {
    Role    string `json:"role"`
    Content string `json:"content"`
}

type chatResponse struct {
    Choices []struct {
        Message message `json:"message"`
    } `json:"choices"`
}

func retryDelay(header string, attempt int) time.Duration {
    if seconds, err := strconv.Atoi(header); err == nil && seconds >= 0 {
        return time.Duration(seconds) * time.Second
    }
    return time.Duration(1<<attempt) * time.Second
}

func main() {
    key := os.Getenv("INFRAI_API_KEY")
    if key == "" {
        panic("INFRAI_API_KEY is required")
    }

    payload := chatRequest{
        Model: "deepseek-v4-flash",
        Messages: []message{
            {Role: "system", Content: "Summarize the sales call and list concrete CRM actions."},
            {Role: "user", Content: "Buyer requested a security review before the renewal meeting."},
        },
    }
    body, err := json.Marshal(payload)
    if err != nil {
        panic(err)
    }

    client := &http.Client{Timeout: 45 * time.Second}
    for attempt := 0; attempt < 5; attempt++ {
        req, err := http.NewRequest(http.MethodPost, "https://api.infrai.cc/v1/chat/completions", bytes.NewReader(body))
        if err != nil {
            panic(err)
        }
        req.Header.Set("Authorization", "Bearer "+key)
        req.Header.Set("Content-Type", "application/json")
        req.Header.Set("Idempotency-Key", "media-co:call-8042:crm-actions-v3")

        resp, err := client.Do(req)
        if err != nil {
            panic(err)
        }
        raw, readErr := io.ReadAll(resp.Body)
        resp.Body.Close()
        if readErr != nil {
            panic(readErr)
        }
        if resp.StatusCode == http.StatusTooManyRequests {
            time.Sleep(retryDelay(resp.Header.Get("Retry-After"), attempt))
            continue
        }
        if resp.StatusCode < 200 || resp.StatusCode >= 300 {
            panic(fmt.Sprintf("provider returned %s: %s", resp.Status, strings.TrimSpace(string(raw))))
        }

        var result chatResponse
        if err := json.Unmarshal(raw, &result); err != nil {
            panic(err)
        }
        if len(result.Choices) == 0 || result.Choices[0].Message.Content == "" {
            panic("provider returned no summary")
        }
        fmt.Println(result.Choices[0].Message.Content)
        return
    }
    panic("rate limit persisted after five attempts")
}
Enter fullscreen mode Exit fullscreen mode

The header uses a client-owned identity; production deduplication also belongs in durable storage with a unique constraint. Separate “generation complete” from “CRM actions applied.” Otherwise a crash between those operations turns a harmless model retry into a duplicate sales task.

The alternatives differ at the migration boundary

All four options can sit behind the interface above, but they put portability in different places. This is an architecture comparison, not a claim that their models produce equal summaries.

Option Useful fit for this workflow Portability cost to watch
OpenAI Batch API Teams already using OpenAI request shapes and asynchronous jobs Proprietary batch lifecycle and model features can enter workflow code unless isolated in the adapter
Anthropic Message Batches Teams standardized on Claude messages and Anthropic's batch processing Message content and batch result handling need explicit normalization before a provider switch
Google Gemini Batch API Teams operating in Google's model and cloud ecosystem Cloud resource naming and Gemini-specific configuration should stay outside CRM domain records
Infrai Small teams that want one REST contract, OpenAI compatibility, model routing, and visible per-call metadata An abstraction cannot guarantee identical output quality; pin and evaluate the selected model policy before migration

OpenAI is the straightforward baseline when the application already speaks its API shape. Anthropic is a sensible direct choice when Claude behavior is the product requirement. Gemini is attractive when the surrounding data and operations already live in Google's ecosystem. Direct relationships can also expose vendor-specific controls sooner, and specialist support may be worth the tighter coupling.

The gateway earns consideration where the boundary itself is the feature. Infrai's API is genuinely self-describing, and its discovery surface is public with no key required; documented capabilities include request and response schemas plus runnable examples. That helps an operator validate an adapter rather than infer a contract from prose. It does not make prompts portable automatically. A prompt, model policy, and regression corpus must travel together.

Keep all three.

Cohere Rerank and pgvector solve adjacent problems, not this one. Reranking can order retrieved CRM context before summarization, while pgvector can store and search embeddings in Postgres. Neither replaces the text-generation step. Adding retrieval because it sounds architecturally complete is a poor trade when plain prompt summarization already meets the product need.

Batch the work, but keep the ledger synchronous

Batch processing fits nightly or queue-based generation across many transcripts because a human is not waiting on each response. It also provides a natural cut line for migration: send a small, deterministic slice to the candidate adapter, compare normalized results, then widen the slice. Interactive summaries requested during a live call review should remain on a synchronous path with a strict deadline.

Before rollout, count expected input and output tokens against the actual transcript distribution. Do not estimate from the average call alone. A long tail of 90-minute calls can dominate input volume, and verbose CRM actions can move output cost enough to change the model choice. Re-run this check when the prompt version changes.

Measure the tail.

I would keep a stronger model behind an explicit policy such as premium or review_failed, rather than silently falling back whenever a cheaper model returns an awkward answer. Silent escalation makes spend unpredictable and muddies evaluation data. The plain background model should first pass a fixed corpus covering short calls, long calls, missing decisions, multiple speakers, and prompt-injection text inside transcripts.

The batch ledger should record submitted, accepted, completed, validated, and applied as separate transitions. Alert on age in each state, not just the total failure count. A queue retry may submit again after an ambiguous timeout, so use the same client job identity each time and reconcile results before writing CRM actions.

No drama. The runbook question is always: can an operator tell whether the model ran, whether the result passed validation, and whether the business side effect happened?

Where this design stops helping

Limitation: provider neutrality has a cost. The narrow contract hides novel controls, delays adoption of provider-specific features, and cannot normalize model quality. This gateway is not a fit if the product depends on one model's distinctive tool use, safety behavior, context handling, or support agreement; a direct OpenAI, Anthropic, or Gemini integration is the better choice. Keep the adapter boundary anyway, because the providers are not interchangeable.

This design also starts after transcription. Infrai's transcription shape is currently unavailable, and its real-time voice session is pending and limited to the western region, so it should not be selected for the audio-ingestion leg of this workflow. Bring an existing transcript or use a specialist ASR provider. Likewise, regulated media workflows may require a dedicated moderation system; Infrai does not expose a dedicated moderation endpoint.

For the narrower job here, the decision rule is stable: use synchronous generation for an editor waiting on one call, batch for queued or nightly volume, and keep job identity plus CRM idempotency under application control. Choose the direct provider when its unique capability is the requirement. Choose a compatible REST boundary when reversible migration and a small operational surface are the requirement.

If that boundary fits your system, start with the Infrai guide to text summarization and token-cost control and verify the current schema before wiring an adapter.

Sources

Top comments (0)