Skip to content

Guides

Process a ticket backlog

Classify or summarize thousands of existing tickets with an asynchronous batch instead of blocking your application with one request at a time.

How the pieces connect

  1. 01Ticket export
  2. 02JSONL worker
  3. 03Mahvash batch
  4. 04Reconciled tickets

A real-world example

Take Kalaland, an online marketplace. It has a year of support tickets that were never categorized, and management wants to know why customers contact support: wrong items, late deliveries, refunds, account problems? Labelling tickets by hand is not realistic, and sending them one by one would compete with live traffic.

With a Mahvash batch, a nightly job exports the tickets, sends them all in one file, and receives a category, a sentiment and a one-line summary for every ticket — in a strict JSON format that can be written straight back into the ticket system.

What the business gets

  • The whole history processed without building a queueing system.
  • Runs in the background. Batch work is processed asynchronously under its own capacity limits, instead of making your application wait.
  • Clean, structured results that feed dashboards and reports directly.
  • Contained failures. A problem with one ticket does not stop the rest, and only failed lines need a retry.

See it with one ticket

StepWhat happensIn this example
1. ExportA nightly job exports ticket text without names or phone numbers.Ticket 10482: “I ordered the blue kettle but the seller sent a red one…”
2. Build the fileOne JSONL line per ticket asks for a category, sentiment and summary in a fixed JSON format.custom_id: ticket-10482
3. SubmitThe file is uploaded with purpose=batch and a batch is created.Status goes from validating to in_progress.
4. CollectWhen the batch finishes, the job downloads the output and error files.{"category": "wrong_item", "sentiment": "negative", …}
5. ApplyValidated labels are written back; a dashboard groups tickets by reason.“Wrong item” becomes a visible, measurable category.

Each line of the input file is a complete Responses request. Here is one line, and the matching line from the output file:

{"custom_id": "ticket-10482", "method": "POST", "url": "/v1/responses", "body": {"model": "auto", "instructions": "Label this support ticket.", "input": "I ordered the blue kettle but the seller sent a red one. I want the correct colour, not a refund.", "text": {"format": {"type": "json_schema", "name": "ticket_label", "strict": true, "schema": {"type": "object", "additionalProperties": false, "required": ["category", "sentiment", "summary"], "properties": {"category": {"type": "string", "enum": ["delivery", "wrong_item", "damaged", "refund", "account", "other"]}, "sentiment": {"type": "string", "enum": ["positive", "neutral", "negative"]}, "summary": {"type": "string"}}}}}}}

The text.format JSON schema makes the model return exactly the fields and categories you define, so the reconciler can validate each result before writing it. The custom_id is what connects the output back to ticket 10482.

Note:The exact envelope of output lines is illustrative. Always match results by custom_id and validate the parsed values before using them.

Why a batch

Backlogs, nightly re-classification and bulk summaries do not need an immediate answer. A batch lets you submit all the work in one file and collect the results later.

  • Your job exports ticket IDs and sanitized text from your system.
  • Mahvash processes each line as an ordinary request, with the same validation, limits and accounting.
  • Your job matches each result back to its ticket by custom_id and applies it.

Create a valid JSONL file

Write one JSON object per line. Every line needs a unique custom_id, method: "POST", the endpoint url (for example /v1/responses or /v1/moderations) and the request body. All lines must use the same endpoint as the batch.

  • Streaming and background requests are not allowed inside a batch.
  • Blank lines, duplicate custom_ids, unknown fields or malformed JSON make the batch fail validation before anything runs.

Upload the finished .jsonl file through /v1/files with purpose=batch.

Submit and track

  1. 1Create the batch with the uploaded file-... ID, the matching endpoint and completion_window: "24h".
  2. 2Save the batch ID. The batch starts in validating, then moves to in_progress once every line is accepted.
  3. 3Poll GET /v1/batches/{batch_id} from a scheduled worker and watch request_counts.
  4. 4When the batch finishes, download output_file_id and error_file_id separately — either may be absent.

Worker implementation

The example builds the JSONL from ticket records, uploads it, creates the batch and returns the batch ID. A separate reconciler reads each output line by custom_id.

Important:Never use line order as the ticket identity. Always join results on custom_id, because partial failures and retries make positions unreliable.

Implementation example

import json, os, requests
api = "https://api.mahvashai.ir"
headers = {"Authorization": "Bearer " + os.environ["MAHVASH_PROJECT_KEY"]}
with open("tickets.jsonl", "w", encoding="utf-8") as target:
    for ticket in tickets.unsummarized():  # Your queue
        target.write(json.dumps({"custom_id": str(ticket.id), "method": "POST", "url": "/v1/responses",
            "body": {"model": "auto", "input": "Summarize this ticket: " + ticket.text}}) + "\n")
with open("tickets.jsonl", "rb") as source:
    uploaded = requests.post(api + "/v1/files", headers=headers,
        data={"purpose": "batch"}, files={"file": source}, timeout=120)
uploaded.raise_for_status()
batch = requests.post(api + "/v1/batches", headers=headers,
    json={"input_file_id": uploaded.json()["id"], "endpoint": "/v1/responses",
          "completion_window": "24h"}, timeout=30)
batch.raise_for_status()
jobs.save_batch_id(batch.json()["id"])  # Poll GET /v1/batches/{id} later

Example response checkpoint

{
  "id": "batch_<id>",
  "object": "batch",
  "status": "validating",
  "input_file_id": "file-<uuid>",
  "output_file_id": null
}

Illustrative response excerpt. Your application owns the surrounding workflow and customer data.

Apply results safely

Show processed, failed and pending counts in your operations view. Write a label or summary to a ticket only after its output line has been parsed and validated, and keep error lines for manual review.

Retries and cancellation

  • A batch can finish with some lines in the error file. Do not resubmit the whole batch — build a new file containing only the failed custom_ids.
  • POST /v1/batches/{batch_id}/cancel stops queued work. Lines that already completed stay completed and are still charged.

Use the same pattern for

  • Product reviews — sentiment and topic labels for every review in your catalog.
  • Call transcripts — overnight summaries of yesterday's calls.
  • Catalog content — rewriting or translating product descriptions in bulk.
  • Content moderation — checking a backlog of user comments with /v1/moderations.
  • Search indexes — creating embeddings for a document archive with /v1/embeddings.