Client for the 'Toggl Track' API
Overview
Read and write time entries, projects and clients of the 'Toggl Track' time tracker https://toggl.com from R, and download its reports. Reads never return silently truncated or empty results: HTTP errors are raised, the hourly quota and rate limits are respected, and paginated reports are checked for completeness.
Breaking changes
- Reports come from the Reports API v3 (#76):
get_summary_report()items no longer givecur,sumandrate(NA); entries without a visible project are grouped under anNAid and name instead of "Private Project"; theuser_agentargument of the report functions is ignored. The columns and types ofget_detailled_report()are unchanged. The rounding set in the Toggl profile is never applied (rounding = 0in every request), as in Reports v2. get_workspace_id()returns the default workspace of the account, no longer always the first one (#23).get_time_entries()fails before any request for a period starting more than 3 months ago (the API refused it anyway) (#13).
Show all changes in 1.0.0Hide the rest of the changes in 1.0.0
create_client()returns the created client (a list), no longer the httr response (#54);toggl_update_entries()sends one PUT per entry and returns the updated entry, or a list of them (#56).- A write refused by the API raises an error (#54);
toggl_start()andtoggl_create()returnNA, with a warning, when the API accepted the entry but its answer could not be read. get_toggl_api_token()fails, outside an interactive session, instead of prompting, and never deletes the stored key (#51).set_toggl_api_token()returns the token invisibly (#84).simplify()is rewritten on the public API of jsonlite (#69).get_detailled_report():cache = FALSEby default,max_pageandmemoise_cache_dirdeprecated (#60, #67); a 7th positional argument now goes tocache.
Other changes
get_detailled_report()reads Reports API v3 (/search/time_entries, enriched with the names of users, projects, clients and tags) instead of Reports v2, with the same columns and types. Pages of 1000 entries (options(togglr.page_size)) instead of 50: a month of a team of 20 is read in one or two requests. The period is checked against the total duration of/search/time_entries/totals, equal before and after the pagination and, up to the rounding of the API (1 s per entry), to the sum of the entries. New argumentuser_ids;user_agentis ignored (#76).- The completeness checks of
get_detailled_report()also treat as a movement any entry received that was edited after the read started: the pages are numbered by row, so a deletion followed by an insertion of the same duration shifts the rows without changing the totals. This replaces thetotal_grandtolerance for running timers, which could hide such a movement (#77). A running timer may only grow by the time between the two totals, measured by the clock of the server from its own start, not by the time spent waiting before the first total. One movement still escapes: an entry deleted and replaced by one of exactly the same duration that is not in the pages received. get_summary_report(),get_weekly_report()andget_dashboard()read Reports API v3 too, with the output of Reports v2. Reports v3 only returns ids: the names of users, projects and clients come from the Track API v9, archived projects included.get_weekly_report()returns an empty tibble for a week without any entry. An unreadable answer to a report request is an error, never an empty report. The amounts of the summary items (cur,sum,rate) are no longer given (NA). Entries without a visible project are grouped with anNAid and name (Reports v2 called them "Private Project") (#76).- With a shared state file, an exhausted quota, or a spread of the requests, recorded by another process while this one was waiting is no longer erased: it is waited for in turn, or fails within
max_wait(#78). - The requests of Reports v3 (
/reports/api/v3/workspace/<id>/) count against the quota of their workspace, like the requests of the API v9 (/workspaces/<id>/): an exhausted Reports quota no longer blocks/me. get_time_entries()is never truncated:/me/time_entriesreturns at most 1000 entries, so a full answer splits the period in two, recursively. A period starting more than 3 months ago (the limit of the API) fails before any request, pointing toget_detailled_report(user_ids = )(#13). Its output gains aclient_namecolumn;project_nameandclient_namecover the archived projects too (#14).get_project_id_and_name()gainsactive(NULL,"false"or"both") and returns 0 rows, not an error, for a workspace without any matching project.sinceanduntilmay be text dates again.get_workspace_id()returns the default workspace of the account (default_workspace_idof/me), and the first workspace only when no default is set; it used to be always the first one. Newget_workspaces()lists every workspace, the default one flagged (#23).get_dashboard()andget_all_project_names()gainuser_ids, to keep the projects some team members worked on (#12).- New
get_project_data(): the data of one project from its id (#28). toggl_start(tags = )sends its tags, now covered by a test (#16).set_toggl_api_token()returns the token invisibly (it was printed when called at top level, e.g. in a script whose output is logged), and checks the new token before deleting the stored one.update_toggl_api_token()asks first: a cancelled input keeps the stored token (#84).- Tests: coverage from 85 % to 93 %, a real multi-process test of
togglr.state_file, no test depending on covr's instrumentation; new workflow measuring the coverage (#84, #32, #33). - DESCRIPTION declares
R (>= 4.1.0)and the minimal versions of its dependencies; the obsoleteinst/dependencies.Ris no longer shipped (#83). - The API token is no longer printed:
create_client()andtoggl_create_project()usedhttr::verbose(), which shows theAuthorizationheader (#50). - Every request times out after
options(togglr.timeout)seconds (120 by default), and network errors (timeout, DNS, connection reset) are retried like a 5xx (#45). get_detailled_report()returns fixed column types (e.g.taskis always text,tidinteger), whatever the content of the page: periods over a year with an empty year no longer fail to combine (#46).- New option
togglr.state_file: the pacing of the reports calls and the knowledge of an exhausted quota are stored in that file, under a lock, and shared by every R process using it (callr / future / targets workers, cron jobs). Without it, the state stays private to the session (#48). - A 402 without quota headers (a feature of a paid plan) now stops at once with the message of the API, instead of blocking every request for an hour. An exhausted quota is remembered per token, as a hash: the token is never stored (#49).
- The
api_tokenandworkspace_idgiven to a function are now used by every call it makes: project, client and dashboard helpers,get_time_entries()(newworkspace_idargument),toggl_start(),toggl_stop(),toggl_create(),toggl_create_project()no longer fall back to the keyring token nor to an extra/workspacesrequest.client_name_to_id()andclient_id_to_name()gain aworkspace_idargument (#51). get_toggl_api_token()no longer deletes the stored key when the keyring cannot be read (e.g. locked under cron); in a non interactive session it fails with a clear message instead of prompting (#51).toggl_start()andtoggl_create()send the start time converted to UTC, instead of removing one hour: entries were one hour off in summer, and more outside Paris (#52, from #44 by @bradyrippon).get_current_duration()counts from the start of the running entry, not from its last edit (at);get_project_total()andget_current_project_task_detail()were affected (#53).- Every write (POST, PUT, PATCH) goes through
toggl_request(), with the policy of the reads: an error of the API is raised with its message (no more "$ operator is invalid for atomic vectors"), the quota and 429 are respected. A POST is never retried after a 5xx or a network error, as it may have been processed.toggl_start()/toggl_create()notify then raise the error;toggl_stop()only notifies STOP once the API accepted it.create_client()now returns the created client (a list) instead of the raw httr response (#54). toggl_create_project()works again: no crash without client, an existing client is reused instead of being created again, itsclient_idis sent, and the body follows the v9 format (private,color,activeare no longer ignored) (#55).toggl_update_entries()sends one PUT per entry (the v9 API takes a single id per PUT), sendsstop(it was accepted then dropped), sends the project asproject_id, and no longer sends the read-onlyat. It returns the updated entry, or a list of them for several ids (#56).get_time_entries()sends its dates in UTC (...Z): the+of a time zone offset was sent unencoded in the url, and read as a space by the server (#57). Entries with several tags are covered by a test (#30).get_detailled_report()splits by calendar year as soon as a period spans 365 days or more across a new year: a 367 days period (bounds included) was sent as is, beyond the one year limit of the reports API v2 (#58).- The pagination of
get_all_projects()is bounded: it stops with an error if the API returns the same page twice, or afteroptions(togglr.max_pages)pages (100 by default), instead of spending the quota in an endless loop (#59). get_detailled_report(max_page = )now warns that the argument is deprecated and ignored (#60).- The reports functions (
get_detailled_report(),get_summary_report(),get_weekly_report(),get_dashboard()) acceptsince/untilas aDate, a date-time (its day in its own time zone) or a text "YYYY-MM-DD", and fail with a clear message, before any request, on anything else or onsinceafteruntil(#61). - Default periods ("one year ago") are computed with
%m-%: they wereNAon February 29th (#62). options(togglr.min_interval)(1 s by default) now spaces every request, reads and writes, retries included, not only the pages of the detailled report (#64).- A period that keeps moving during the pagination is read up to
options(togglr.report_attempts)times (3 by default, 2 before) (#65). - An entry seen twice during the pagination keeps its most recently updated version, not the first one read (#66).
- The reports cache (
cache = TRUE) lives in its own folder, thereportssub-folder ofrappdirs::user_cache_dir("togglr")oroptions(togglr.cache_dir), apart from the memoise files of 0.2.x. New argumentcache_dirand functionclean_cache();memoise_cache_dirandclean_memoise_cache()are deprecated (#67). - The quota announced by every answer (
x-toggl-quota-remaining) is used: at 0, the next request waits for the reset (or fails withinmax_wait) instead of receiving a 402; with fewer thanoptions(togglr.quota_spread)(10) requests left, they are spread until the reset (#68). simplify()no longer calls an internal function of jsonlite (getFromNamespace()): it uses the publictoJSON()/fromJSON(), at full precision, with the same result (#69).globalVariables()no longer declares function names (case_when,full_join,tribble) nor unused names, which could hide a missing import (#70).- Every request sends the user agent
togglr/<version>in its header;get_dashboard()no longer sends a hardcodedapi_test(#71). Theuser_agentargument of the report functions is kept for compatibility and ignored. get_workspace_id()fails with its message when the account has no workspace, instead of "subscript out of bounds". New tests cover the edge cases of the pagination, of the quota and of the cache (#73).- Package metadata and documentation: new title and description, no more
LazyDatawithout data, targeted imports only (no more wholedplyr,httr,keyring,assertthat,getPass),get_detailled_report()has its own help page, the vignette no longer runsinstall.packages()duringR CMD check.R CMD checkis clean: 0 error, 0 warning, 0 note (#74). toggl_create_project()(and sotoggl_start()on a new project) works again in a workspace without any client:get_all_client_info()returns theidandnamecolumns, even with no rows (review of #75).- The reports cache is kept per token (as a hash): a period fetched with one token is no longer returned to another token of the same workspace (review of #75).
- The spreading of the last requests of the quota waits at most
max_wait, with a message, and only slows down the token that is short of quota (review of #75). - The quota is remembered per token and per scope (the workspace or organization of the url, or the user for
/meand/workspaces): an exhausted workspace no longer blocks/meor another workspace (review of #75). - A request processed by the API is returned even if its quota cannot be recorded in the state file (warning): a time entry is no longer reported as not started, then created twice (review of #75).
- One pass on the state file per request instead of three, and a read no longer rewrites it (review of #75).
- A time entry accepted by the API but whose answer cannot be read is reported as started (with a warning, id
NA), not asNOT STARTED(review of #75). - A state file that cannot be replaced is an error instead of a silently lost update, and the temporary file is removed (review of #75).
- Network errors are no longer retried when the backoff exceeds
max_wait(review of #75).
From NEWS.md in the release
Install
Health
- OK13 OK2026-10-22
- NOTE13 NOTE2026-06-09
- ERROR12 NOTE, 1 ERROR2026-06-08
- NOTE3 OK, 11 NOTE2026-03-10
Documentation
- Help pages
- 42includes a package overview page
- Examples that run
- 0%12 never run
- Exported functions with examples
- 29%
- Documented parameters
- 92%
- Return-value docs
- 100%
- Help pages citing references
- 0%
- togglr HTML
Downloads
Repository
- PRs merged (all time)
- 77%
- Median days to close, 50 most recently updated closed issues
- 0
- Median days to close, 50 most recently updated closed PRs
- 0
Stars over time
Forks over time
Releases over time
Issues over time
PRs over time
Commits over time
9 development-tooling and community-health practices detected across 6 families in the upstream repository
Checks run against github.com/thinkr-open/togglr on 2026-10-18.
The R CMD check workflow runs on Linux, macOS and Windows, and on R-devel
Show all practicesHide 2 practices
This repository shows signs of AI coding tools.
- Claude Codefirst seen 2026-10-14Pull request it wrote: opened by a project memberCommits: at least 46 crediting it in the message
- Claude Opus 5.546 commits · Oct 2026
Dependencies
Code & Tests
People & History
Pick two releases to compare their code metrics.
- RR 4.6.0 released · 2026-04-24
- RR 4.5.0 released · 2025-04-11
- RR 4.4.0 released · 2024-04-24
- 1.0.0Latest2024-02-19 · current release · diff ↗ ·
added 3 exports
clean_cache, get_project_data, get_workspaces - 0.2.12024-02-20 · diff ↗
- 0.22024-02-15 · diff ↗ ·
removed 1 export
get_project_task_detailadded 2 exports
clean_memoise_cache, get_current_project_task_detail - RR 4.3.0 released · 2023-04-21
- 0.1.992022-07-11 · diff ↗ ·
added 7 exports
get_detailled_report, get_summary_report, get_weekly_report, get_workspace_users, simplify, to_humain, toggl_update_entries - RR 4.2.0 released · 2022-04-22
- RR 4.1.0 released · 2021-05-18
- RR 4.0.0 released · 2020-04-24
- RR 3.6.0 released · 2019-04-26
- 0.1.332018-08-14 · diff ↗ ·
removed 1 export
open_toggl_websiteadded 4 exports
get_project_id_and_name, get_time_entries, open_toggl_website_app, open_toggl_website_profile - RR 3.5.0 released · 2018-04-23
- 0.1.32017-12-07
- RR 3.4.0 released · 2017-04-21
Package metadata
- First published
- 2017-12-07
- Total releases
- 6 / 9 yrs
- License
- GPL-3 OSI
- Minimum R
- ≥ 4.1.0
- Download size
- 70 KB
Cite
Cite this package
Run in R for the authors' preferred citation:
citation("togglr")This package has no citation file, so citation() returns this entry built from DESCRIPTION.
Cite the R Observatory
For a number measured here: a download total, a coverage figure, an archival date.
From data release v2026-10-22, which the citation names so these numbers can be found later. More on citing and the projects behind them.