Summarize Text with an API in Python

How to summarize text with a text summarization API: a curl request, then a Python script that summarizes support tickets to JSON and CSV. Try it free.

  • 7 min read
  • Published March 7, 2026
  • Updated September 13, 2026
Article featured image

To summarize text with an API, POST the text and a sentence count to the Text Summarizer API and read data.summary from the response. Below is the one-request version, then a Python script that turns a CSV of support tickets into short summaries tagged with language and sentiment.

The minimal request

curl -X POST https://api.apiverve.com/v1/textsummarizer \
  -H "x-api-key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "text": "A news article can include accounts of eyewitnesses to the happening event. It can contain photographs, accounts, statistics, graphs, recollections, interviews, polls, debates on the topic, etc. Headlines can be used to focus the reader'\''s attention on a particular (or main) part of the article. The writer can also give facts and detailed information following answers to general questions like who, what, when, where, why and how.",
    "sentences": 2
  }'

Response:

{
  "status": "ok",
  "error": null,
  "data": {
    "originalWords": 67,
    "summaryWords": 30,
    "percentDifference": 44.78,
    "summary": "Headlines can be used to focus the reader's attention on a particular (or main) part of the article. A news article can include accounts of eyewitnesses to the happening event."
  }
}

Two parameters. text is required. sentences is optional and defaults to 3.

summary is the field you actually want. originalWords, summaryWords and percentDifference are premium fields, so on some plans they won’t be in the response. Don’t build logic that assumes they exist.

No key yet? Get a free API key — the quickstart covers where to find it.

Build a support ticket summarizer in Python

Here’s the plan. You have a tickets.csv export with an id and a body column. For each ticket, the script will:

  1. Detect the language with Text Language
  2. Summarize it with the Text Summarizer
  3. Tag the tone with Sentiment Analysis
  4. Write everything to summaries.json and summaries.csv

Only dependency is requests:

pip install requests
export APIVERVE_API_KEY="your_key_here"

Step 1: One helper for every call

All three APIs share the same base URL, the same x-api-key header, and the same {status, error, data} envelope. So write the HTTP part once, with retries baked in.

import os
import time
import requests

API_KEY = os.environ["APIVERVE_API_KEY"]
BASE = "https://api.apiverve.com/v1"
RETRYABLE = {429, 500, 502, 503, 504}


class APIError(Exception):
    pass


def call(api_id, payload, attempts=4):
    for attempt in range(attempts):
        try:
            resp = requests.post(
                f"{BASE}/{api_id}",
                json=payload,
                headers={"x-api-key": API_KEY},
                timeout=30,
            )
        except requests.RequestException as exc:
            if attempt == attempts - 1:
                raise APIError(f"{api_id}: network error: {exc}")
            time.sleep(2 ** attempt)
            continue

        if resp.status_code in RETRYABLE and attempt < attempts - 1:
            time.sleep(2 ** attempt)
            continue

        body = resp.json() if resp.content else {}
        if resp.status_code != 200 or body.get("status") != "ok":
            raise APIError(f"{api_id}: HTTP {resp.status_code}: {body.get('error')}")
        return body["data"]

Notice what doesn’t get retried: 401 and 403. A bad key or a key without access to that API won’t fix itself on the fourth try, so fail fast and read the message. A 429 means you’re rate limited or out of credits — the backoff handles the first case, and the final error message tells you if it’s the second. More patterns in API error handling that actually works.

The timeout is 30 seconds on purpose. Summarization is slower than a lookup, and a 5-second timeout will throw away requests that were about to succeed.

Step 2: Language and summary

def detect_language(text):
    data = call("textlanguage", {"text": text})
    return data.get("language"), data.get("iso")


def summarize(text, sentences=2):
    data = call("textsummarizer", {"text": text, "sentences": sentences})
    return data["summary"]

Text Language returns a lowercase name and an ISO 639-1 code:

{ "status": "ok", "error": null, "data": { "language": "french", "iso": "fr" } }

Why bother detecting language before summarizing? Because once you know it, you can route. Send fr tickets to the team that reads French, spot-check summary quality per language, or skip languages you haven’t tested yet. Knowing is cheap; guessing is how a Portuguese ticket ends up in the wrong queue for three days.

Step 3: Sentiment tag

def sentiment(text):
    data = call("sentimentanalysis", {"text": text})
    if data.get("isNegative"):
        return "negative"
    if data.get("isPositive"):
        return "positive"
    return "neutral"

The response carries more than those two booleans:

{
  "status": "ok",
  "error": null,
  "data": {
    "comparative": 0.25,
    "sentimentText": "positive",
    "sentiment": 3,
    "isPositive": true,
    "isNegative": false,
    "normalizedScore": 0.3
  }
}

isPositive and isNegative are enough for a triage label. normalizedScore (-1 to 1) is premium, so treat it as a bonus if it shows up. Run sentiment on the full ticket, not the summary — you want the customer’s tone, not the tone of two extracted sentences.

Step 4: Process the CSV

import csv
import json


def process(path_in="tickets.csv"):
    results = []
    with open(path_in, newline="", encoding="utf-8") as f:
        for row in csv.DictReader(f):
            text = (row.get("body") or "").strip()
            record = {"id": row["id"], "language": None, "iso": None,
                      "summary": None, "sentiment": None, "error": None}

            if len(text.split()) < 40:
                record["summary"] = text  # already short; don't pay to shorten it
            try:
                record["language"], record["iso"] = detect_language(text)
                if record["summary"] is None:
                    record["summary"] = summarize(text)
                record["sentiment"] = sentiment(text)
            except APIError as exc:
                record["error"] = str(exc)

            results.append(record)
    return results

Two decisions in there worth calling out.

Short tickets skip the summarizer. “Can’t log in, reset email never arrives” doesn’t need condensing, and a 2-sentence summary of a 2-sentence ticket is just the ticket. Tune the 40-word threshold to your data.

Errors are recorded per row instead of crashing the run. One malformed ticket shouldn’t kill a batch of 2,000 — you want the output file with an error column you can filter on and re-run.

Step 5: Write JSON and CSV

def write_outputs(results):
    with open("summaries.json", "w", encoding="utf-8") as f:
        json.dump(results, f, ensure_ascii=False, indent=2)

    with open("summaries.csv", "w", newline="", encoding="utf-8") as f:
        writer = csv.DictWriter(f, fieldnames=results[0].keys())
        writer.writeheader()
        writer.writerows(results)

    failed = sum(1 for r in results if r["error"])
    print(f"{len(results)} tickets, {failed} failed")


if __name__ == "__main__":
    results = process()
    if results:
        write_outputs(results)

ensure_ascii=False matters the moment a ticket contains accented characters. Without it, your JSON fills up with é escapes.

Run it:

python summarize_tickets.py
# 312 tickets, 0 failed

The calls run one at a time. That’s slow for big exports but stays well clear of rate limits. If you need speed, a small ThreadPoolExecutor works — keep the worker count below your plan’s rate limit or the retries will eat the gain.

When summaries mislead

The code is the easy part. A few things to get right before agents start triaging off summaries:

Always keep the original one click away. A summary is a preview, not a replacement. Store the ticket ID next to every summary and link to the full text in whatever UI shows it. Details that look minor to a summarizer — an order number, “this is the third time” — are often the whole point.

Rambling input gives weaker summaries. A well-structured article with a clear lead condenses cleanly. A stream-of-consciousness email with three complaints and a tangent doesn’t. That’s a property of the input, not a bug, and support tickets are exactly this kind of text.

Label summarized content as summarized. If agents or users don’t know they’re reading a condensed version, they’ll blame you when a detail is missing.

Spot-check before you trust it. Pull 20 real tickets, read each summary next to its source, and adjust sentences. Two sentences suits a triage list. A longer escalation note often needs four. Sentiment labels need the same scrutiny — why your sentiment analysis is wrong covers the usual failure modes.

Cache by content. Tickets don’t change after they’re closed. Hash the body, store the summary against the hash, and never pay to summarize the same text twice.

Next steps

Same key works for all three. Get a free API key and point the script at your own ticket export.

Give your software a way to act on the world.

Scaling in production?

The same key runs from your first prototype to millions of calls — on APIVerve's rails, backed by an uptime SLA.

See pricing