Using the Timeular Public API
We recently released our Public API v3 at developers.timeular.com. This latest release includes the ability to work with our new Spaces API.
Spaces empower teams to collaborate and track time together, so adding it to the public API was an important step in further helping our users to customize and improve their workflows.
With our public API, users can build their own third party integrations, custom automation tools and reports, which are tailored to their exact needs.
Another exciting new feature within our public API are Webhooks, which we will cover in a future post. Webhooks finally enable two-way communication with our API, opening the door to much richer automations and community-built integrations.
Webhooks also enables us to finally integrate with tools such as Zapier.
In this post, we will look at how to use our public API for simple data fetching and manipulation. We will use Rust as a language for building the example. The underlying ideas and the structure of the example will work for every other language the same way, however.
Also, if you go to developers.timeular.com, due to the fact that we use Postman to publish our API docs, you can switch the language of the examples using the “Language” picker on top. This way, you can see how to make requests to our API in many different languages supported by Postman.
All you need, in any language, is an HTTP client library, which supports HTTPS and a Timeular account.
Let’s get started!
First, we add some dependencies in a new project, which we create using “cargo init”.
We’ll use the reqwest library for making HTTP requests. We’ll use the async API, which uses the Tokio runtime underneath. We also need serde and serde_json for serializing and deserializing requests and responses to and from JSON.
The once_cell crate is just a helper we use here for making a lazy static HTTP client, so we don’t have to pass it to every method.
In this example, we will execute several steps using the Timeular public API:
- Fetching our user profile
- Fetch our spaces
- Fetch activities
- Start a new tracking
- Stop the tracking
- Generate a report and save it in a local file
To start off, let’s import some packages and define some constants:
For convenience, we create a “catch-all” error type, as we’re not going to deal with error handling in this small example.
Then, as mentioned above, we use once_cell to create a lazy request::Client, which we can access globally.
Finally, we define constants for the BASE_URL of the Timeular public API and for the file name of the report, we’re going to save later on.
Now that all the infrastructure is in place, let’s start with authentication.
First, if you don’t have one yet, you need to create an account here and activate it.
Then, using the dashboard, create your API credentials and copy the API secret to a safe place, as it won’t be shown to you again.
We’ll keep it simple in terms of credentials for this small application and just expect there to be two environment variables:
- TMLR_API_KEY – The TImeular API Key
- TMLR_API_SECRET – The Timeular API Secret
With these credentials, we can check out developers.timeular.com to see how we can sign in.
Using the API version marked as “CURRENT” (API v3 at the time of writing this), navigate to “Authentication” and then to “Sign-in with API Key & API Secret”.
The documentation tells us to send a POST request with the credentials and we’ll get a token back, which we can then use in a header for authorization.
To achieve this, let’s get the environment variables first and pass them to a request function in main:
The sign_in function looks like this:
Let’s step through it. We use std::env to read the expected environment variables and if one of them is not set, we panic with an error message. So far, so good.
Then we define SignInRequest as a serializable struct containing the credentials. We need to use Serde’s rename_all functionality here, so that the actual JSON uses camelCase names, which is what the API expects.
Within the sign_in function, we create the JSON body out of the given token and secret and use our shared CLIENT (the reqwest::Client instance), to send a POST request.
The url helper just concatenates the BASE_URL defined in the beginning with the path we’re interested in:
We set the JSON body inside the request and send it off. Since we’re using the async version of reqwest, we’ll have to await the response.
Then, we can use the json helper on the response to deserialize it the the above defined SignInResponse struct, which simply holds the authentication token. We need to await this step as well, as it asynchronously reads the body.
We handle all errors by propagating them up the chain using the ? operator. This means, that if an error happens here – for example because the JSON isn’t valid, the program will crash, as we’re propagating the errors up to main, which returns a Result with our defined catch-all error type.
If everything worked out, we simply return the token, which we can now use to make authenticated requests.
Our next goal is to fetch some data about ourselves, such as our user profile and our spaces.
For that purpose, we create two more functions – fetch_me and fetch_spaces. Each of these functions gets passed our newly created authentication token.
First, let’s look at fetch_me, which uses the Me endpoint, which can be found under User Profile -> User -> Me.
We define a response object, which contains our user id, name, email and the id of our default space.
In the fetch_me function, we simply call the HTTP client with a get towards /me, set the Authorization header to our auth token, prefixed with “Bearer “ and send it off, parsing the response to a MeResponse.
This is essentially the whole magic we need to do, to make authenticated requests to the Timeular API – set the Authorization header with the proper values.
With this in mind, fetching our spaces is easy:
We do the same thing again. The only difference is, that we’re calling a different URL, which can be found in User Profile -> Space -> Spaces with Members on developers.timeular.com.
In this case, since the response is a list with some nested objects, we need to create a couple more types, but all in all, the process is the same and we end up with a fully filled out list of our spaces and their members.
Keep in mind, that you’ll only see other member’s data, if you’re the admin of a space, otherwise you’ll see only yourself and the admins in the response.
That’s it for profile data, let’s start tracking some time!
Time Tracking data
To track time, we first need activities, so we start by fetching those.
The fetch_activities function follows the style of the previous two calls. We first define the data models for handling the response and then send an authenticated request to /activities. The time tracking related endpoints can be found under Time Tracking in the public API docs.
Make sure there is at least one activity in existence, otherwise create a new one using the API, or one of our apps.
The next step is to start a new tracking and stop it again. We will use hard-coded timestamps for this, but you could use the current time as well. Just make sure the time entry is at least one minute long, as that’s the lower limit at the time of writing this in API v3.
We check, if our activities list has any values, and if so, we call the start_tracking method with the first activity in the list and our auth token.
The payload, as we can see in the public API docs, is rather simple – we just send a started_at timestamp, formatted like this “”2020-08-03T04:00:00.000″”. Important to note is, that the API always expects UTC timestamps, so make sure to convert your local time to UTC before sending it, to make sure the times are correct.
We get back a TrackingResponse object:
The tracking response has a tracking id, an activity id, the started time we sent and a note. This note has a text element and a list of tags, or mentions. We won’t go into much detail here when it comes to tags, or mentions, but be sure to check out the public API docs regarding this topic.
Tags and mentions are a way to further categorize and label your time entries, so you can find them more quickly and improve your flexibility in terms of analytics.
Once the data types are set up, we send a POST request to the tracking start URL, setting the activity id as a path variable, so our API knows which activity to start tracking.
Stopping the tracking is very similar to starting it, but we send a stopped_at date this time and we don’t have to specify the activity id in the URL, since there can only be one active tracking at any point in time.
The StopTrackingResponse contains a full TimeEntry, which is similar to a Tracking, except that it also contains a Duration object, which holds the from and to dates of the finished time entry.
Cool! So now we can create time entries, let’s get a report from the API to finish this tutorial off.
There are multiple ways we can generate a report using the Timeular public API. If we go to Time Tracking -> Reports, we can see two endpoints.
One is for creating a file report, which creates either a CSV, or an XLSX file. The other option is a JSON report.
In this case, we’ll opt for a simple CSV report, which we can then use for analysis. One thing to note here is that file reports are a Pro feature in Timeular. However, if you just created your account, you can just start a subscription trial in the app, which will give you Pro for a month for free.
We create the generate_report function, which will fetch the report and save it to the file defined in the REPORT_FILE variable.
As in the previous API calls, we get an auth token and send a GET request to the endpoint. In this case, we have to put the from and to dates into the URL as path parameters. We’re hard coding these values here, but if you were to dynamically generate them, make sure they are in UTC and according to the above shown format.
For this special endpoint, we also need to set the time zone and the report will be converted to the time zone we specified, this makes it easier in your own analysis, since you don’t have to manually convert the times to your time zone.
Once we get a successful response, we use Tokio’s fs::write to write the returned bytes into the file.
That’s it! We can now open the file and see if everything is as we expect it to be.
You can run it with:
The whole code for this code example can be found here.
This concludes our first journey into the world of the Timeular public API. I hope we were able to give you a glimpse of the many possibilities using the API opens up.
In this article, we looked at poking at the Timeular API to get data back. In the next one, we will check out our brand new Webhooks API, which enables two-way-communication, giving you even more power to build automation tools with Timeular.
Stay tuned. 🙂