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. Authenticate with a bearer token instead of an admin login. Use the API to import data into a data warehouse, business intelligence tool, or 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 customer-entered personally identifiable information (PII), such as a name, notes, or party size. They only include IDs, status, and lifecycle timestamps.

Pagination: When nextCursor is not null, pass it as cursor on the next request. Keep the same from, to, and filter values. A null nextCursor means you have reached the end of the range.

This endpoint accepts the same filters as /v1/tickets: from and to are required, and queueId, locationId, and status are repeatable. It does not accept cursor or limit. The complete matching range streams as one CSV response, so the 500-row page limit does not apply.

Column order is stable, but parse columns by header name rather than fixed position. 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 uses Transfer-Encoding: chunked. A 90-day export with 100,000 rows does not need to remain in memory at either end. Send it directly to a file or parser.

Every endpoint that takes from and to rejects a range over 90 days with 400. If you need a longer history, request shorter ranges, such as one request per week. Use lifecycle timestamps such as calledAt and completedAt to calculate wait and service times without requesting the same rows twice.

Each token allows 60 requests per minute. The limit applies to the token, not the IP address. Every response includes:

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.

A demo organisation has an additional limit of 50 requests per day. The per-minute limit also applies. You can use the demo API to test a complete integration by listing queues, requesting tickets, and creating a CSV export. The daily limit is not enough for production use. If you exceed it, the API returns 429 with a body that identifies the cause:

{
"error": "demo_quota_exceeded",
"limit": 50,
"resetAt": "2026-01-02T09:00:00.000Z"
}

Demo CSV exports are also marked, so an exported file cannot be mistaken for production data: the filename is prefixed demo-, the response carries X-Jonot-Demo: 1, and a trailing demo column is appended to the CSV. Paid exports are unchanged — no extra column, no extra header. Subscribing to a paid plan lifts the daily quota and drops the markings.

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.