Python REST API Quickstart
Query Nexatron from Python using the REST API directly with httpx or requests.
Authentication
All API requests require an API key passed in the Authorization header:
Authorization: Bearer nxa_your_api_key_here
Generate an API key from Settings > API Keys in the Nexatron dashboard.
Using httpx (Recommended)
import httpx
BASE_URL = "https://api.nexatron.io"
API_KEY = "nxa_your_api_key_here"
client = httpx.Client(
base_url=BASE_URL,
headers={"Authorization": f"Bearer {API_KEY}"},
timeout=60.0,
)
Execute a Query
response = client.post("/api/v1/query", json={
"question": "What were total sales last quarter?",
})
response.raise_for_status()
result = response.json()
print(result["generated_sql"])
# SELECT SUM(amount) AS total_sales
# FROM orders
# WHERE order_date >= '2026-01-01' AND order_date < '2026-04-01'
print(result["result_data"])
# [{"total_sales": 1284350.00}]
print(result["content"])
# "Total sales last quarter were $1,284,350.00."
Query with Conversation Context
Pass a conversation_id to maintain context across follow-up questions:
# First question
r1 = client.post("/api/v1/query", json={
"question": "Show revenue by region",
})
conversation_id = r1.json()["conversation_id"]
# Follow-up question (references previous results)
r2 = client.post("/api/v1/query", json={
"question": "Which region grew the fastest?",
"conversation_id": conversation_id,
})
print(r2.json()["content"])
Stream Results
Use server-sent events for real-time streaming:
import json
with client.stream(
"POST",
"/api/v1/query/stream",
json={"question": "Monthly revenue for 2025"},
) as response:
# The server emits `id:`, `event:` and `data:` lines per SSE frame. The event
# NAME is in the `event:` field — it is not duplicated inside the JSON — so
# track it as the lines stream past, then act when its `data:` arrives.
event_type = None
for line in response.iter_lines():
if not line:
continue
if line.startswith("event: "):
event_type = line[7:].strip()
continue
if not line.startswith("data: "):
continue
payload = json.loads(line[6:])
if event_type == "stage":
print(f" [{payload['stage']}] {payload['message']}")
elif event_type == "sql":
print(f" SQL: {payload['sql']}")
elif event_type == "data":
print(f" Rows: {len(payload['rows'])}")
elif event_type == "content":
print(f" Summary: {payload['text']}")
elif event_type == "done":
print(f" Conversation: {payload['conversation_id']}")
elif event_type == "error":
print(f" Error: {payload['message']}")
Using requests
import requests
BASE_URL = "https://api.nexatron.io"
headers = {"Authorization": "Bearer nxa_your_api_key_here"}
# Execute a query
response = requests.post(
f"{BASE_URL}/api/v1/query",
headers=headers,
json={"question": "Top 10 customers by lifetime value"},
timeout=60,
)
response.raise_for_status()
result = response.json()
for row in result["result_data"]["rows"]:
print(f" {row['customer_name']}: ${row['lifetime_value']:,.2f}")
Export Results
Export as CSV / Excel / PDF
Run the question first, then export the answer message by ID:
result = client.post("/api/v1/query", json={
"question": "All orders from last month",
}).json()
export = client.get(
f"/api/v1/query/messages/{result['message_id']}/export",
params={"format": "csv"}, # csv, xlsx, or pdf
)
with open("orders.csv", "wb") as f:
f.write(export.content)
Exporting re-runs the answer's query rather than downloading the rows shown on screen, so the file is the whole result and not the first page of it. It runs under your own access (the same row policies and column masking you would get by asking the question again) and is bounded by your workspace's query row limit. A PDF renders at most 5,000 rows.
Every file carries a commented governance header, and its Rows: line tells you
how many rows the file contains and whether that is the complete result. Read
that line rather than assuming: when your workspace's row limit is what stopped
the read, the header says so (source row cap reached; true total not counted)
instead of reporting the bound as the total. If you asked for a specific number
("top 10 regions"), the export honours it and returns ten rows.
If the answer could not be re-run (for example it combined several sources, or its warehouse is disconnected), the header says the rows are the stored snapshot saved with the answer and why they could not be refreshed.
Exporting a saved answer (GET /api/v1/reports/{id}/export) is different: it
serves the rows stored with that answer rather than re-reading the source. Its
header says so too, and it states the 5,000-row PDF limit on the occasions that
limit is what shortened the document.
Export as JSON
response = client.post("/api/v1/query", json={
"question": "All orders from last month",
})
import json
with open("orders.json", "w") as f:
json.dump(response.json()["data"], f, indent=2)
Load into pandas
import pandas as pd
response = client.post("/api/v1/query", json={
"question": "Daily active users for the past 90 days",
})
result = response.json()
df = pd.DataFrame(result["result_data"]["rows"])
print(df.describe())
Async Usage
import httpx
import asyncio
async def main():
async with httpx.AsyncClient(
base_url="https://api.nexatron.io",
headers={"Authorization": "Bearer nxa_your_api_key_here"},
timeout=60.0,
) as client:
response = await client.post("/api/v1/query", json={
"question": "Revenue by product category",
})
result = response.json()
print(result["content"])
asyncio.run(main())
Error Handling
try:
response = client.post("/api/v1/query", json={
"question": "Show me the data",
})
response.raise_for_status()
except httpx.HTTPStatusError as e:
if e.response.status_code == 401:
print("Invalid or expired API key")
elif e.response.status_code == 422:
error = e.response.json()
print(f"Validation error: {error['detail']}")
elif e.response.status_code == 429:
# Retry-After is sent only when the server knows the wait; otherwise back off and retry.
print("Rate limited. Retry after:", e.response.headers.get("Retry-After") or "not sent")
else:
print(f"API error {e.response.status_code}: {e.response.text}")
except httpx.TimeoutException:
print("Request timed out. Try a simpler query or increase the timeout.")
Next Steps
- SDK Quickstart for the TypeScript SDK
- React Widget for embedding in web apps
- API Reference for all endpoints