28times API
Time Series API
Inserting, querying and deleting time series data points is done via an HTTP API with JSON objects as input and output.
All requests need an API key to be provided as HTTP header:
Authorization: Bearer <apikey>
You can get an API key for a time series on the time series page by clicking on the "Create API Key" button. The time series affected by the request is identified by the API key, as each time series has its own API key.
API Endpoints
The API endpoints are
https://api.28times.com/series/<seriesid>/<operation>
where <seriesid> is the id of the time series, as shown when you click on your time series. A time series id looks like s3269286212.
<operation> is insert, prune, list or count, see below.
As a shortcut you can use this API endpoint
https://api.28times.com/series/self/<operation>
without <seriesid>, as the API key already identifies the time series.
Please note that you cannot insert or prune data points of derived time series.
All examples assume time zone Europe/Berlin for the time series.
API Insert Endpoint
The API Insert POST endpoint is
https://api.28times.com/series/<seriesid>/insert or
https://api.28times.com/series/self/insert
Input
The input is a JSON object in this format
{
"time": "2021-06-21 16:00:00Z",
"value": 7.9,
"conflict": "replace",
"full": "discard"
}
All fields except value are optional.
-
time: specifies the time of the data point in one of the supported formats. If you omit it, the current time of the API call is used, just like when you provide the special time stamp"now". -
value: is a numeric value of the time stamp. -
conflict: defines the conflict resolution strategy. A conflict is the insertion of a data point when there is already a data point for that time stamp. Strategies arereplace: replaces the existing value with the one given in the API.keep: keeps the stored value and ignores the value given in the API.error: (this is the default) also keeps the stored value, but treats the API call as error. Use this if you do not expect conflicts to occur, so that you can easily identify problems.
-
full: defines the action taken when the maximum number of data points allowed for the time series would be exceeded with the insertion of the new data point.discard: removes the oldest data point in the time series after inserting the new one, in order to stay within the limit.block: does not insert the time point given in the API in case it is really new, and not just replacing the value of an existing one or being ignored by akeepconflict resolution.error: (this is the default) does the same asblock, but generates an error in that case.
As a special shortcut for the input, you can just provide a plain value. The default is used for the missing input fields.
514
is the same as
{
"value": 514
}
and the same as
{
"time": "now",
"value": 514,
"conflict": "error",
"full": "error"
}
Output
The output of the call is a JSON object confirming either a success or showing an error.
Success
A successful response looks like this:
{
"outcome": "success",
"inserted": [
{"time": "2021-06-21 18:00:00+02:00", "value": 7.9}
]
}
Possible fields in the response object are:
inserted: shows the data point that has been inserted. Data points are always shown as object withtimeandvalueproperties, where time is in the series’ time zone.replaced: shows a data point that has been replaced by the newly inserted one. The time stamp is the same as the inserted one in this case, but you can see the old value that is no longer stored.unchanged: shows a data point that you tried to insert, but it was already stored with the same value.blockedshows a data point that you tried to insert, but it has been blocked because there was already a data point with that time stamp.discarded: shows a data point that has been removed from the time series via thefull = discardpolicy. If you use this policy, there will always be adiscardedfield in the output. An empty array shows that nothing has been discarded.
Please note that while these fields are arrays, so they could technically hold more than one object, they will always contain at most one object in this version of the API.
Error
An error response looks like this:
{
"outcome": "error",
"title": "Duplicate data point",
"detail": "A data point with this time stamp already exists",
"blocked": [
{"time": "2023-11-04 11:12:13+02:00", "value": 16}
]
}
An error returns always a title, a detail message, and optionally an object identifying the problem.
These are the error types and their HTTP return codes:
400 Bad Request: Either some formatting error in the JSON input, or an insert request for a derived time series has been made.401 Unauthorized: An invalid or missing API key.409 Conflict: Inserting the data point was rejected due to an already existing data point or due to the time series limit. The affected data point is returned in theblockedfield, so that you can see how your input has been interpreted.422 Unprocessable Entity: Some invalid parameter found, for example no value or non-numeric value, or invalid time stamp, or invalid conflict resolution policy.429 Too Many Requests: The number of API requests made per day has exceeded the limit for this time series, see below.500 Internal Server Error: Something outside your scope went wrong.
Example
An example of a valid API call in curl is
curl https://api.28times.com/series/self/insert \
-X POST \
-H "Authorization: Bearer <api-key>" \
-H "Content-Type: application/json" \
-d '{"time": "12.06.2017 4:00pm UTC", "value": "3.4"}'
which would return in case of success:
{
"outcome": "success",
"inserted": [
{"time": "2017-06-12 18:00:00+02:00", "value": 3.4}
]
}
API List Endpoint
The API List POST endpoint is
https://api.28times.com/series/<seriesid>/list or
https://api.28times.com/series/self/list
Input
The input is a JSON object in this format
{
"from": "2021-06-21 18:00:00 Europe/Berlin",
"before": "2021-07-10 00:00:00Z",
"order": "earliest",
"limit": 100
}
fromorafter: specifies the start of the time interval from which you want to get data points in one of the supported formats, except you cannot omit the time part to specify an interval.fromincludes start point (works like >=), andafterexcludes it (>).beforeoruntil: specifies the end of the time interval.beforeexcludes end point (<), anduntilincludes it (<=).- If you omit start or end point, or set it to
null, the start or end point of the interval is open. If you omit both, the complete time series will be returned. - You always need to give a time for defining intervals, in order to avoid ambiguities like this:
is2021-06-21 03:00included or not in"after": "2021-06-21"? order: specifies the order in which you want to get data points. Order can beearliest,latest(sorted by time stamps),highestorlowest(sorted by values). Default is earliest.
If you specifyhighestorlowest, you may addearliestorlatestas second criteria:"order": ["highest", "earliest"]. Default is again earliest in that case.limit: specifies the number of data points in the result. Default is to return all data points in the interval.- The request body might be omitted, in which case all data points are returned in the default
earliestorder.
Output
The output of the call is an array of JSON objects with the data points from the interval together with the query parameters.
Success
A successful response looks like this:
{
"outcome": "success",
"from": "2021-06-21 18:00:00+02:00",
"before": "2021-07-10 02:00:00+02:00",
"points": [
{"time": "2021-06-21 20:00:00+02:00", "value": 7.9},
{"time": "2021-06-22 16:00:00+02:00", "value": 8.8},
{"time": "2021-06-23 11:00:00+02:00", "value": 9.1},
...
]
}
Time stamps are always returned in the standard format according to the time zone defined for the time series.
"value" shows the numerical value of the time series, but in case of derived series, that can also be "Infinity",
Error
An error response looks like this:
{
"outcome": "error",
"title": "No authorization header",
"detail": "HTTP Header in this format expected: Authorization: Bearer <apikey>",
}
An error returns always a title, a detail message, and optionally an object identifying the problem.
These are the error types and their HTTP return codes:
400 Bad Request: Some formatting error in the JSON input.401 Unauthorized: An invalid or missing API key.422 Unprocessable Entity: Some invalid parameter found, for example an invalid time stamp.429 Too Many Requests: The number of API requests made per day has exceeded the limit for this time series, see below.500 Internal Server Error: Something outside your scope went wrong.
Example
An example of a valid API call in curl is
curl https://api.28times.com/series/self/list \
-X POST \
-H "Authorization: Bearer <api-key>" \
-H "Content-Type: application/json" \
-d '{"from": "12.06.2017 4:00pm UTC"}'
which would return in case of success:
{
"outcome": "success",
"from": "2017-06-12 18:00:00+02:00",
"points": [
{"time": "2017-06-12 18:00:00+00:00", "value": 3.4},
{"time": "2055-01-01 10:00:00+00:00", "value": -10.9}
]
}
API Prune Endpoint
The API Prune POST endpoint is
https://api.28times.com/series/<seriesid>/prune or
https://api.28times.com/series/self/prune
Using the self API shortcut for the prune endpoint is disencouraged, as it is more error-prone than the <seriesid> variant, which needs the seriesid to match the API key.
Input
The input is a JSON object in this format
{
"from": "2021-06-21 16:00:00Z",
"before": "2021-07-10 00:00:00Z",
"order": "earliest",
"limit": 100
}
Parameters are the same as in list API, see above, except that
start and end cannot be omitted in this endpoint, in order to prevent unrecoverable mistakes. However, they can be set to null.
Output
The output of the call is an array of JSON objects with the data points of the interval that have been deleted together with the prune parameters.
Success
A successful response looks like this:
{
"outcome": "success",
"from": "2021-06-21 18:00:00+02:00",
"before": "2021-07-10 02:00:00+02:00",
"deleted": [
{"time": "2021-06-21 20:00:00+02:00", "value": 7.9},
{"time": "2021-06-22 16:00:00+02:00", "value": 8.8},
{"time": "2021-06-23 11:00:00+02:00", "value": 9.1},
...
]
}
Time stamps are always returned in the standard format according to the time zone defined for the time series.
Error
An error response looks like this:
{
"outcome": "error",
"title": "No authorization header",
"detail": "HTTP Header in this format expected: Authorization: Bearer <apikey>",
}
An error returns always a title, a detail message, and optionally an object identifying the problem.
These are the error types and their HTTP return codes:
400 Bad Request: Either some formatting error in the JSON input, or a prune request for a derived time series has been made.401 Unauthorized: An invalid or missing API key.422 Unprocessable Entity: Some invalid parameter found, for example an invalid time stamp.429 Too Many Requests: The number of API requests made per day has exceeded the limit for this time series, see below.500 Internal Server Error: Something outside your scope went wrong.
Example
An example of a valid API call in curl is
curl https://api.28times.com/series/s3269286212/prune \
-X POST \
-H "Authorization: Bearer <api-key>" \
-H "Content-Type: application/json" \
-d '{"from": "12.06.2017 4:00pm UTC", "before": null}'
which would return in case of success:
{
"outcome": "success",
"from": "2017-06-12 18:00:00+02:00",
"deleted": [
{"time": "2017-06-16 18:00:00+02:00", "value": 3.4},
{"time": "2055-01-01 10:00:00+02:00", "value": -10.9}
]
}
API Count Endpoint
The API Count POST endpoint is
https://api.28times.com/series/<seriesid>/count or
https://api.28times.com/series/self/count
Input
The input is a JSON object in this format
{
"from": "2021-06-21 16:00:00Z",
"before": null
}
- Parameters for defining the interval are the same as in list API.
- The request body might be omitted, in which case the number of all data points is returned.
Output
The output of the call is a JSON objects with the number of data points in the interval together with the count parameters.
Success
A successful response looks like this:
{
"outcome": "success",
"from": "2021-06-21 18:00:00+02:00",
"count": 177
}
Error
An error response looks like this:
{
"outcome": "error",
"title": "No authorization header",
"detail": "HTTP Header in this format expected: Authorization: Bearer <apikey>",
}
An error returns always a title, a detail message, and optionally an object identifying the problem.
These are the error types and their HTTP return codes:
400 Bad Request: Some formatting error in the JSON input.401 Unauthorized: An invalid or missing API key.422 Unprocessable Entity: Some invalid parameter found, for example an invalid time stamp.429 Too Many Requests: The number of API requests made per day has exceeded the limit for this time series, see below.500 Internal Server Error: Something outside your scope went wrong.
Example
An example of a valid API call in curl is
curl https://api.28times.com/series/self/count \
-X POST \
-H "Authorization: Bearer <api-key>" \
-H "Content-Type: application/json" \
-d '{"from": "12.06.2017 4:00pm UTC", "before": "1.1.2100 00:00"}'
which would return in case of success:
{
"outcome": "success",
"from": "2017-06-12 16:00:00+02:00",
"before": "2100-01-01 00:00:00+01:00",
"count": 2480
}
Time Series API Quota
Each time series has a quota of daily API requests, see also pricing. The quota is reset daily at midnight UTC.
If the quota is exceeded, an error 429 Too Many Requests is returned, and no data is entered or modified in the time series.
You can get the number of remaining requests at this API GET endpoint:
https://api.28times.com/series/<seriesid>/quota or
https://api.28times.com/series/self/quota
which will return:
{
"outcome": "success",
"limit": 30,
"remaining": 5,
"reset": "2027-07-21 00:00:00Z"
}
These requests do not count against your quota.