Use the XQL query APIs. Start a query with /public_api/v1/xql/start_xql_query, which returns an execution ID. Fetch the results with /public_api/v1/xql/get_query_results in JSON. That call returns up to 1,000 results inline; anything bigger comes back as a stream ID you pass to /public_api/v1/xql/get_query_results_stream. Authentication uses two headers: your API key in Authorization and its ID in x-xdr-auth-id.
Cortex XDR collects detailed endpoint telemetry, plus network and identity data in many deployments. Prime Core has more than 24,000 unique detection rules, mapped to 676 MITRE ATT&CK techniques. Below is how to get data out, sanitize it, and investigate it for free in LogTotal.
Before you start
An API key. Create one in the Cortex XDR settings (the standard key type is simplest) with a role that can run XQL queries. Note the key and its ID.
Your API URL. It’s shown when you create the key, in the form https://api-<tenant>.xdr.<region>.paloaltonetworks.com.
License and quota. The XQL APIs need a Cortex XDR Pro license. Queries draw on your XQL quota, and you can run up to four API queries in parallel.
The dataset. Endpoint telemetry is in xdr_data, with fields such as agent_hostname, event_type, action_process_image_command_line and actor_process_image_name. Build the query in the Query Builder first.
Export with the XQL APIs
XDR="https://api-<tenant>.xdr.<region>.paloaltonetworks.com"
H1="Authorization: $XDR_API_KEY"
H2="x-xdr-auth-id: $XDR_API_KEY_ID"
# 1. start the query (timeframe in epoch milliseconds)
QID=$(curl -sS -X POST "$XDR/public_api/v1/xql/start_xql_query" -H "$H1" -H "$H2" -H 'Content-Type: application/json' \
-d '{"request_data": {"query": "dataset = xdr_data | filter agent_hostname = \"WS-042\" | fields _time, agent_hostname, event_type, action_process_image_name, action_process_image_command_line, actor_process_image_name", "timeframe": {"from": 1792368000000, "to": 1792454400000}}}' \
| jq -r '.reply')
# 2. wait for the results
while :; do
RESP=$(curl -sS -X POST "$XDR/public_api/v1/xql/get_query_results" -H "$H1" -H "$H2" -H 'Content-Type: application/json' \
-d "{\"request_data\": {\"query_id\": \"$QID\", \"pending_flag\": true, \"format\": \"json\"}}")
[ "$(echo "$RESP" | jq -r '.reply.status')" != "PENDING" ] && break
sleep 5
done
# 3. up to 1,000 results inline; more through the stream
STREAM=$(echo "$RESP" | jq -r '.reply.results.stream_id // empty')
if [ -n "$STREAM" ]; then
curl -sS -X POST "$XDR/public_api/v1/xql/get_query_results_stream" -H "$H1" -H "$H2" -H 'Content-Type: application/json' \
-d "{\"request_data\": {\"stream_id\": \"$STREAM\", \"is_gzip_compressed\": false}}" > xdr_ws042_1019.jsonl
else
echo "$RESP" | jq -c '.reply.results.data[]' > xdr_ws042_1019.jsonl
fi
The timeframe above is 2026-10-19 00:00 UTC to 2026-10-20 00:00 UTC.
Upload, sanitize and review in LogTotal
- Upload. Open logtotal.com and drop in the file.
- Sanitize.
- Review.
When it goes wrong
| Symptom | Likely cause | Fix |
|---|---|---|
| 401 | Key and key ID swapped, or the key expired | Key in Authorization, ID in x-xdr-auth-id |
| License error | No XDR Pro license | XQL APIs need XDR Pro |
| Quota errors | XQL quota used up, or more than four parallel queries | Wait; narrow the query |
| Exactly 1,000 results | Stream not used | Fetch the rest with the stream ID |
| Query errors | XQL syntax | Test in the Query Builder first |
FAQ
Can I export incidents and alerts?
Yes, through separate incident and alert APIs. For LogTotal, export the telemetry behind them.
What about raw third-party logs?
If you ingest them into Cortex, they live in their own datasets and come out the same way.