Export TimeTonic to JSON: getTableValues, Filters, Paging, and OCR for Shared Exports
To export TimeTonic to JSON, skip the Share menu and call the API. The TimeTonic UI exports a table view as CSV or XLSX, and an Excel or Word template can turn rows into documents, but none of those produce JSON. The API does: a POST to https://timetonic.com/live/api.php with req=getTableValues, a Sesskey, and the table’s catId returns the table’s values as JSON. Add format=rows to get one record per row, offset and maxRows to page through large tables, and filterRowIds to export only what a view or filter selects. OCR only comes in for the documents that left TimeTonic as a PDF, a screenshot, or a printout.
This follows the same pattern as the Clockify to JSON and Typeform to JSON guides: use the API when you can reach the data, and OCR when the only copy is pixels.
Pick the route by what you have
| What you have | Route | What comes back |
|---|---|---|
| Access to the workspace and an API key | getTableValues |
JSON, by column or by row |
| Need a subset, such as one view or a date range | getTableValues with filterRowIds |
Filtered JSON |
| Just need a spreadsheet | Share, then Export CSV or Export XLSX | File in the workspace messenger |
| A PDF or Word document generated from a template | OCR | Markdown or typed JSON |
| A screenshot of a table or a printed record | OCR | Markdown or typed JSON |
Step 1: create a read-only Sesskey
Every request carries a Sesskey, TimeTonic’s API key. According to TimeTonic’s guide to creating and managing API keys, you create one from your user profile, under Settings, in the API Keys (SessKey) section. You name the key, tick its rights, and confirm with your account password.
Four rights are set independently: write, read data, read messages, and read calendar events. For an export, tick only “Can read data from TimeTonic”. A read-only key cannot change or delete rows if it leaks.
Two details matter before you write any code:
- The key inherits the account’s access. A key made from an admin account reaches everything that admin sees. TimeTonic recommends a dedicated account for integrations, limited to the data it actually needs.
- Workspaces can block API access. The key lives at account level, but each workspace has its own API access setting, and it also depends on the plan. If a valid key returns nothing for one workspace, check that setting before you debug your code. TimeTonic’s API presentation also notes that specific pricing may apply to API use.
Step 2: find the identifiers
A getTableValues call needs five things:
version: the API version. TimeTonic’s Postman example uses6.49n.u_c: the user code that matches the Sesskey.o_u: the OAuth user. For a plain read with your own key, it is the same user code.sesskey: the key from Step 1.catId: the numeric ID of the table. Tables used to be called categories, hence the name.
Finding catId is the awkward part. TimeTonic’s API example says you currently get it by opening the table in the browser with developer tools open and reading the getCarnetFormCategory request that fires when the tab loads. Do it once per table and write the IDs down.
Step 3: call getTableValues
The endpoint takes a form-encoded POST, not a JSON body:
curl -X POST "https://timetonic.com/live/api.php" \
-d "req=getTableValues" \
-d "version=6.49n" \
-d "u_c=$TT_USER" \
-d "o_u=$TT_USER" \
-d "sesskey=$TT_SESSKEY" \
-d "catId=$TT_CAT_ID" \
-d "format=rows" \
-d "offset=0" \
-d "maxRows=100"
By default, results come back organized by column. Passing format=rows returns values grouped by row instead, which is the shape most JSON consumers want. TimeTonic’s example warns the row format makes the response heavier, so keep page sizes sensible.
Save the first response and read it before you write a parser. The response includes the numeric column IDs (field_id) that you will need for filters, plus row metadata such as rowInfosLength. Column names in the UI can change, so map by field_id in code and keep a small lookup from ID to your own field name.
Paging through a large table
getTableValues pages with offset and maxRows. In TimeTonic’s Postman collection, offset is described as the page number and maxRows as the number of records per page. Confirm on your own table whether offset steps by page or by row before you trust a loop, because getting it wrong silently skips or repeats records. A loop that stops when a page comes back short:
import os, json, requests
URL = "https://timetonic.com/live/api.php"
base = {
"req": "getTableValues",
"version": "6.49n",
"u_c": os.environ["TT_USER"],
"o_u": os.environ["TT_USER"],
"sesskey": os.environ["TT_SESSKEY"],
"catId": os.environ["TT_CAT_ID"],
"format": "rows",
"maxRows": 100,
}
pages, offset = [], 0
while True:
r = requests.post(URL, data={**base, "offset": offset})
r.raise_for_status()
body = r.json()
if body.get("status") != "ok":
raise RuntimeError(body)
pages.append(body)
if body.get("rowInfosLength", 0) < base["maxRows"]:
break
offset += 1 # check against your table: page number or row offset
json.dump(pages, open("timetonic-export.json", "w"), indent=2)
This keeps each raw page as returned. Flatten it into your own schema in a second step, once you know exactly which keys your table produces.
Step 4: export only what you need with filterRowIds
A full table dump is often more than you want. The filterRowIds guide describes two ways to narrow getTableValues.
The simplest is to reuse a view that already has filters set up. Pass its ID:
{ "applyViewFilters": 12345 }
The other is to define the filter yourself. This one returns rows where a date column is on or after 1 January 2026:
{
"applyViewFilters": {
"filterGroup": {
"operator": "and",
"filters": [
{
"id": "tmpId",
"field_id": "1735324",
"filter_type": "date",
"json": { "predicate": "on or after", "operand": "2026-01-01" }
}
]
}
}
}
Send it as the filterRowIds form field, JSON-encoded as a string. filter_type must match the column type: text for text, email, and formula columns, int or float for numbers, date or datetime for dates, link for linked tables, enum for selection lists. Groups can nest, so and and or conditions can be combined.
One trap is worth knowing. TimeTonic says a filter with wrong syntax returns a valid but empty response with rowInfosLength: 0. Your script will not see an error, it will see an empty table. Treat zero rows from a filtered export as suspicious until you have checked the tag names and column IDs.
Route 3: CSV and XLSX from the Share menu
If a spreadsheet is enough, TimeTonic’s help on sharing table data covers it: open the view, click Share, and pick Export CSV or Export XLSX. The file appears in the workspace messenger rather than downloading directly, and an “include files” option adds attachments. The export follows the current view, so set filters first. Converting that CSV to JSON is a few lines of code, but you lose the numeric column IDs, so prefer the API for anything you will rerun.
TimeTonic also keeps daily backups. Its data recovery article says a TimeTonic Backup workspace receives a ZIP of each table in CSV format on days when the table changes. That is useful for a one-off historical snapshot.
When OCR is the right step
TimeTonic is often used to generate documents: quotes, delivery notes, reports, and contracts built from Excel or Word templates. Those documents travel by email and get printed, and sometimes they are the only copy you can reach. Use OCRskill for TimeTonic data only in that situation:
- PDF or Word documents generated from a TimeTonic template and sent to you
- Screenshots of a table view shared in chat by someone with access you do not have
- Printed records that were filled in, signed, or stamped by hand
- Archives from a workspace that has since closed
For a page that is mostly a table, Markdown keeps rows and columns intact so you can parse them yourself:
curl https://api.ocrskill.com/get-key.json
curl https://api.ocrskill.com/ocr \
-H "Authorization: Bearer sk-your-key-here" \
-F "file=@timetonic-quote-2026-118.pdf"
For a document with a header and a few values, such as a quote number, customer, date, and total, use structured extraction with POST /ocr.json?fields=.... It returns named JSON keys and fails closed with a 400 when a required field is missing; mark optional keys with a trailing ?. Check the OCR JSON API reference for which fields the catalog supports, and see OCR output as structured JSON or XML for how to choose between free text and typed fields.
Check OCR output against something printed on the same page. Re-add line totals against the document total, and compare record counts with any count the template prints. A mismatch usually points at a row split across a page break.
One schema for API and OCR rows
Whichever route a record came from, land it in the same shape:
{
"source": "timetonic_api | timetonic_csv | ocr",
"table": "Quotes",
"row_id": "147159906",
"fields": {
"quote_number": "Q-2026-118",
"customer": "Acme SRL",
"date": "2026-10-14",
"total": 1840.00
},
"exported_at": "2026-10-24T08:00:00Z"
}
Use the TimeTonic row ID when you have it, so a rerun updates rows instead of duplicating them. OCR rows will not have one, so leave it empty and keep source on every record. That way a reviewer knows which values were read from an image and deserve a second look.
Conclusion
The reliable way to export TimeTonic to JSON is getTableValues with a read-only Sesskey, format=rows, and a paging loop you have verified on your own table. Reuse a view’s filters to export a subset, and treat an empty filtered result as a possible syntax error, not proof the table is empty. The Share menu covers spreadsheets, and the daily backup workspace covers historical CSV snapshots. What none of that reaches is the PDFs, printouts, and screenshots that TimeTonic templates produce once they leave the workspace. For those, get a free OCRskill key, check totals against the page, and feed the results into the same schema as your API rows.
