Skip to content

Read-only Analytics API

The api.jonot.io/v1/* HTTP API gives read-only access to your organisation's ticket and queue data — no admin login required, just a bearer token. Use it to pull data into a data warehouse, a BI tool, or a custom dashboard.

  1. Open admin.jonot.io/settings/integrations.
  2. Click the API tokens tab.
  3. Click Create token, give it a name (e.g. "Power BI"), and confirm.
  4. Copy the token immediately — it is shown once and cannot be recovered. If you lose it, revoke it and create a new one.

Tokens are prefixed jot_ and never expire on their own; revoke them from the same tab when they're no longer needed.

Authorization: Bearer jot_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
StatusMeaning
401Missing, malformed, unknown, or revoked token.
402Valid token, but the organisation doesn't have the API feature enabled.
429Rate limit exceeded — see Rate limits below.
400Invalid query parameters, or a date range over 90 days.

Returns your organisation's locations and queues, unfiltered and unpaginated:

Terminal window
curl https://api.jonot.io/v1/queues \
-H "Authorization: Bearer jot_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
{
"locations": [
{
"id": "loc_…",
"name": "Downtown",
"slug": "downtown",
"queues": [
{
"id": "q_…",
"name": "Main Queue",
"slug": "main-queue",
"status": "ACTIVE"
}
]
}
]
}

Use the returned id values to filter /v1/tickets and the CSV export by queueId / locationId.

Paginated ticket read, scoped to your organisation.

ParamRequiredRepeatableNotes
fromyesnoISO-8601, inclusive lower bound on createdAt.
toyesnoISO-8601, exclusive upper bound. Max 90-day span.
queueIdnoyesRepeat the param to filter multiple queues.
locationIdnoyesRepeat the param to filter multiple locations.
statusnoyesOne of WAITING, CALLED, COMPLETED, CANCELED, SKIPPED, NO_SHOW.
cursornonoOpaque value from the previous page's nextCursor.
limitnonoDefault 100, max 500.
Terminal window
curl "https://api.jonot.io/v1/tickets?from=2026-06-01T00:00:00Z&to=2026-06-08T00:00:00Z&status=COMPLETED" \
-H "Authorization: Bearer jot_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
{
"items": [
{
"id": "tkt_…",
"number": 42,
"queueId": "q_…",
"locationId": "loc_…",
"status": "COMPLETED",
"createdAt": "2026-06-01T09:14:02.000Z",
"calledAt": "2026-06-01T09:20:11.000Z",
"completedAt": "2026-06-01T09:24:47.000Z",
"cancelledAt": null,
"skippedAt": null,
"noShowAt": null,
"calledByDeviceSessionId": "dev_…"
}
],
"nextCursor": "eyJjcmVhdGVkQXQi…"
}

Rows never include the ticket's bearer hash or any customer-entered PII (name, notes, party size) — only ids, status, and the lifecycle timestamps.

Pagination: when nextCursor is non-null, pass it as cursor on the next request to continue from where you left off (same from/to/filters). A null nextCursor means you've reached the end of the range.

Same filters as /v1/tickets (from/to required, queueId/locationId/status repeatable) minus cursor/limit — the whole matching range streams as one CSV response, so there's no 500-row page limit to work around for a bulk pull.

Column order (stable, do not rely on header names for positional parsing changing — but the header row is always present and matches this list exactly):

id,number,queueId,locationId,status,createdAt,calledAt,completedAt,cancelledAt,skippedAt,noShowAt,calledByDeviceSessionId
Terminal window
curl "https://api.jonot.io/v1/exports/tickets.csv?from=2026-06-01T00:00:00Z&to=2026-07-01T00:00:00Z" \
-H "Authorization: Bearer jot_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" \
-o tickets.csv

The response streams (Transfer-Encoding: chunked) so a 90-day/100k-row export doesn't need to buffer in memory on either end — pipe it straight to a file or a parser.

Every endpoint that takes from/to rejects a span over 90 days with 400. Pull data incrementally (e.g. one call per week) if you need a longer history — the ticket lifecycle timestamps (calledAt, completedAt, …) let you reconstruct wait/service durations without re-fetching the same rows twice.

60 requests/minute per token (not per IP — the budget travels with the token). Every response carries:

X-RateLimit-Remaining: 42
X-RateLimit-Reset: 1751328000000

A 429 additionally carries Retry-After (seconds). Back off and retry after that window; a scheduled sync every few minutes comfortably stays under the limit.

import requests
import pandas as pd
TOKEN = "jot_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
BASE = "https://api.jonot.io/v1"
HEADERS = {"Authorization": f"Bearer {TOKEN}"}
def fetch_tickets(frm: str, to: str) -> pd.DataFrame:
rows = []
cursor = None
while True:
params = {"from": frm, "to": to, "limit": 500}
if cursor:
params["cursor"] = cursor
res = requests.get(f"{BASE}/tickets", headers=HEADERS, params=params, timeout=30)
res.raise_for_status()
body = res.json()
rows.extend(body["items"])
cursor = body["nextCursor"]
if not cursor:
break
return pd.DataFrame(rows)
df = fetch_tickets("2026-06-01T00:00:00Z", "2026-07-01T00:00:00Z")
df["waitSeconds"] = (
pd.to_datetime(df["calledAt"]) - pd.to_datetime(df["createdAt"])
).dt.total_seconds()
print(df.groupby("queueId")["waitSeconds"].mean())

Or read the CSV export directly — pandas handles the streaming response transparently:

df = pd.read_csv(
f"{BASE}/exports/tickets.csv?from=2026-06-01T00:00:00Z&to=2026-07-01T00:00:00Z",
storage_options={"Authorization": f"Bearer {TOKEN}"},
)
  1. In Power BI Desktop: Get Data → Web.
  2. Choose Advanced, and build the URL with your date range, e.g. https://api.jonot.io/v1/exports/tickets.csv?from=2026-06-01T00:00:00Z&to=2026-07-01T00:00:00Z.
  3. Under HTTP request header parameters, add a header named Authorization with the value Bearer jot_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx.
  4. Click OK — Power BI detects the CSV and opens the Table Preview.
  5. Click Load (or Transform Data first if you want to set column types — createdAt/calledAt/etc. import as text; convert them to Date/Time in Power Query).
  6. Set a scheduled refresh in the Power BI service if you're pulling on a cadence; keep the range comfortably under 90 days per refresh.