ANSWERS · FILES AND FORMATS

I have a JSON export of Jira issues (or Zendesk tickets). How do I search it like a help-center search?

AUTHOR · XERJ documentation team · REVIEWED BY · XERJ engineering team · PUBLISHED · 2026-08-21 · UPDATED · 2026-10-20
SHORT ANSWER · UPDATED 2026-10-20

I have a JSON export of support tickets; how do I search it like a real help-center search? Index the exported JSON on local disk with one xerj autoindex run. In a captured run a description phrase returned 36 hits with per-ticket locators such as tickets:e4, and a status aggregation returned 6 buckets of exactly 30.

Key number: A status terms aggregation returned 6 buckets of exactly 30 tickets each. (as of 2026-08-21 · source: the captured run documented on this page)

TL;DR — XERJ answers a Zendesk ticket search from the exported JSON on local disk, with 1 xerj autoindex run. In a captured run, a phrase from a ticket description returned 36 hits with per-ticket locators such as tickets:e4. A status aggregation returned 6 buckets of exactly 30.

PASTE THIS TO YOUR AI AGENT — IT DOES THE REST Act as a coding agent. Read https://xerj.org/llms.txt, save a Zendesk incremental ticket export to a local folder as tickets.json, ticket_comments.json and users.json, start a local XERJ node, run `xerj autoindex ./zendesk-export --url http://127.0.0.1:9200 --prefix zd --progress plain`, POST a match_phrase on description, POST a terms aggregation on status with size 0, and report the hit locators, the bucket counts and the difference between the bucket total and track_total_hits.
RUN THIS XERJ COMMAND xerj autoindex ./zendesk-export --url http://127.0.0.1:9200 --prefix zd --progress plain Index a saved Zendesk export folder from local disk.
RUN THIS XERJ COMMAND curl -s -XPOST http://127.0.0.1:9200/zd-*/_search -H 'content-type: application/json' -d '{"query":{"match_phrase":{"description":"aardvark billing dispute"}},"size":5,"_source":["ax_path","ax_locator","id","subject","status","priority"],"track_total_hits":true}' Full-text search the ticket description and get a per-ticket locator back.
RUN THIS XERJ COMMAND curl -s -XPOST http://127.0.0.1:9200/zd-json-tickets/_search -H 'content-type: application/json' -d '{"size":0,"aggs":{"by_status":{"terms":{"field":"status","size":20}}},"track_total_hits":true}' Count tickets per status, exactly, and read the total beside the buckets.
RUN THIS XERJ COMMAND curl -s -XPOST http://127.0.0.1:9200/zd-json-tickets/_search -H 'content-type: application/json' -d '{"query":{"terms":{"tags":["escalated"]}},"size":10,"_source":["id","status","tags"],"track_total_hits":true}' Filter the multi-valued tags field for one tag value.

Index the saved export

Zendesk's incremental export gives you JSON with a tickets array, a count and an end_of_stream flag. XERJ reads that file as ordinary JSON and gives each exported file its own index.

xerj autoindex ./zendesk-export --url http://127.0.0.1:9200 --prefix zd --progress plain

The captured run read 3 files into 3 datasets and 246 documents live: zd-json-tickets with 181 documents, zd-json-ticket-comments with 61 and zd-json-users with 4. There were 0 junk files.

The fixture generator wrote that payload to Zendesk's incremental-export shape. Zendesk never ran on the host, and no account was connected, so this page tests the exported JSON and not a live integration.

A description phrase finds the exact ticket

A match_phrase on description returned 36 hits. Each hit named the file, the position inside the tickets array, and the ticket's own fields.

fieldexample value
ax_pathtickets.json
ax_locatortickets:e4
id5
subjectAardvark billing dispute on invoice #5
statusclosed
prioritynormal
curl -s -XPOST 'http://127.0.0.1:9200/zd-*/_search' \
  -H 'content-type: application/json' \
  -d '{"query":{"match_phrase":{"description":"aardvark billing dispute"}},"size":5,"_source":["ax_path","ax_locator","id","subject","status","priority"],"track_total_hits":true}'

The locator is what makes this useful to an agent. The value tickets:e4 names element 4 of the tickets array. A follow-up read therefore opens 1 ticket instead of the whole export.

Status counts are exact

A terms aggregation on status returned 6 buckets of exactly 30 documents each. The keys were closed, hold, new, open, pending and solved. XERJ reported doc_count_error_upper_bound 0 and sum_other_doc_count 0, because every aggregation in XERJ is exact.

{"by_status": {"buckets": [{"key": "closed",  "doc_count": 30},
                           {"key": "hold",    "doc_count": 30},
                           {"key": "new",     "doc_count": 30},
                           {"key": "open",    "doc_count": 30},
                           {"key": "pending", "doc_count": 30},
                           {"key": "solved",  "doc_count": 30}],
               "doc_count_error_upper_bound": 0, "sum_other_doc_count": 0}}

The buckets total 180 while the same request reported 181 hits. The extra document is the whole-file document for tickets.json, which carries no status. Compare the bucket total with track_total_hits whenever the number is the deliverable.

The fields a ticket produces

XERJ inferred the types from the values, with no mapping written by hand.

fieldinferred type
descriptionsemantic_text
subject, raw_subjecttext
status, priority, type, tags, via_channel, custom_fieldskeyword
created_at, updated_at, generated_timestampdate
id, requester_id, assignee_id, organization_id, group_idlong
is_public, has_incidents, allow_channelbackboolean

The date typing is what makes a range query over created_at work straight after the run. The default embedder in XERJ is lexical feature hashing, so a semantic_text field written by that embedder cannot connect a query to a synonym. Neural embeddings are opt-in through --embed-mode neural.

What this capture does not show

XERJ has no Zendesk connector, no API token flow and no sync job. The node observed 0 distinct non-loopback peers across 185 samples over its whole life. Request the export from Zendesk yourself, save it to disk, then index the folder.

If you flatten the export to JSONL, a line at or above 16 MB is skipped and counted as junk (MAX_LINE, 16 << 20, in engine/crates/xerj-autoindex/src/extract/mod.rs). The rest of the file still indexes.

XERJ runs single-node here, with no replication and no failover. Every number above comes from RUN-B, captured on 2026-08-21. The binary was a ci-test profile build, so no wall-clock figure from this run is published as a performance number.

FAQ

I have a JSON export of support tickets. How do I search it like a real help-center search?

Save the export to disk, index the folder, then send a match_phrase on the description field. The captured run returned 36 hits, each carrying the ticket id, subject, status and priority.

How do I search through an export of Zendesk tickets?

Save the export to local disk and run xerj autoindex on the folder. The captured node observed 0 non-loopback peers over its whole life.

How do I search a Jira export without Jira?

It is the same job: the export is JSON on disk, so the fields are typed and an issue key, a status or a comment token becomes a filter. This capture used a Zendesk export, so the Jira field names were not verified here.

Are the status counts exact or estimated?

Exact. The captured aggregation returned 6 buckets of 30 with doc_count_error_upper_bound 0 and sum_other_doc_count 0.

Why do the status buckets total 180 when the search says 181?

The extra document is the whole-file document for tickets.json, which carries no status. Compare the bucket total with track_total_hits before you report a count.

How do I filter on ticket tags?

XERJ typed tags as a multi-valued keyword, so a terms filter with the tag value returns the matching tickets. custom_fields is keyword too.

Do ticket comments become searchable too?

Yes. The captured run put ticket_comments.json into its own index, zd-json-ticket-comments, with 61 documents.

Evidence

Related