curl --request GET \
--url https://api.cloud.corbado.io/v1/observe/timeSeries \
--header 'Authorization: Bearer <token>'import requests
url = "https://api.cloud.corbado.io/v1/observe/timeSeries"
headers = {"Authorization": "Bearer <token>"}
response = requests.get(url, headers=headers)
print(response.text)const options = {method: 'GET', headers: {Authorization: 'Bearer <token>'}};
fetch('https://api.cloud.corbado.io/v1/observe/timeSeries', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.cloud.corbado.io/v1/observe/timeSeries",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "GET",
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"net/http"
"io"
)
func main() {
url := "https://api.cloud.corbado.io/v1/observe/timeSeries"
req, _ := http.NewRequest("GET", url, nil)
req.Header.Add("Authorization", "Bearer <token>")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.get("https://api.cloud.corbado.io/v1/observe/timeSeries")
.header("Authorization", "Bearer <token>")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.cloud.corbado.io/v1/observe/timeSeries")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Get.new(url)
request["Authorization"] = 'Bearer <token>'
response = http.request(request)
puts response.read_body{
"panel": {
"times": [
123
],
"namedTimeSeries": [
{
"name": "<string>",
"dimensions": {},
"values": [
123
]
}
],
"customTags": [
"<string>"
],
"live": [
true
],
"facets": {}
}
}{
"error": {
"message": "Validation failed",
"details": [
{
"field": "projectID",
"message": "required"
}
]
}
}Get time series data
Retrieves time series data for a project (grouped by time)
Required API key permission: observe:timeSeries:read.
curl --request GET \
--url https://api.cloud.corbado.io/v1/observe/timeSeries \
--header 'Authorization: Bearer <token>'import requests
url = "https://api.cloud.corbado.io/v1/observe/timeSeries"
headers = {"Authorization": "Bearer <token>"}
response = requests.get(url, headers=headers)
print(response.text)const options = {method: 'GET', headers: {Authorization: 'Bearer <token>'}};
fetch('https://api.cloud.corbado.io/v1/observe/timeSeries', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.cloud.corbado.io/v1/observe/timeSeries",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "GET",
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"net/http"
"io"
)
func main() {
url := "https://api.cloud.corbado.io/v1/observe/timeSeries"
req, _ := http.NewRequest("GET", url, nil)
req.Header.Add("Authorization", "Bearer <token>")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.get("https://api.cloud.corbado.io/v1/observe/timeSeries")
.header("Authorization", "Bearer <token>")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.cloud.corbado.io/v1/observe/timeSeries")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Get.new(url)
request["Authorization"] = 'Bearer <token>'
response = http.request(request)
puts response.read_body{
"panel": {
"times": [
123
],
"namedTimeSeries": [
{
"name": "<string>",
"dimensions": {},
"values": [
123
]
}
],
"customTags": [
"<string>"
],
"live": [
true
],
"facets": {}
}
}{
"error": {
"message": "Validation failed",
"details": [
{
"field": "projectID",
"message": "required"
}
]
}
}Authorizations
Use an Observe API key from the management console. The key selects the project and must grant the permission listed on the operation. Keep this key on your server.
Query Parameters
Range start as a local datetime without timezone, formatted as YYYY-MM-DDTHH:MM:SS (e.g. "2026-05-20T00:00:00").
^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}$"2026-05-20T00:00:00"
Range end as a local datetime without timezone, formatted as YYYY-MM-DDTHH:MM:SS (e.g. "2026-05-20T00:00:00").
^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}$"2026-05-20T00:00:00"
Aggregation interval. hourly, daily and monthly read the precalculated data. 1m and 5m are dynamic intervals: they are never stored, so the definition's query is run live over the whole range (at most 8 hours, buckets aligned to UTC) and every bucket is flagged live. Dynamic intervals cannot be combined with experimentRunID or flowListID.
Interval of a time series read: the precalculated intervals plus the dynamic intervals (1m, 5m) that are always computed live
hourly, daily, monthly, 1m, 5m Time series name
Comma-separated dimension keys to keep; collapsed dimensions are summed. Allowed: applicationId, browser, os, osVersion, customTag1..6, t1..t10. Omitted keeps full detail.
"t2,applicationId"
Flow-series filter: engaged keeps rows whose flow reached a subflow or nested flow, or resolved a decision; non-engaged keeps the rest, i.e. flows that were only routed and abandoned. Only valid for login, signup, enrollment, and recovery flow series. Defaults to all.
all, engaged, non-engaged "engaged"
Optional experiment run ID (format exr-<number>). When set (and the run evaluates this series), each
returned series is partitioned by the run's variants: every series carries an extra "variant" dimension
(the variant ID, or "-" for the unexposed remainder), so summing across "variant" reproduces the
unscoped series. When empty, the response is unchanged.
"exr-123456"
Experiment exposure scope. "exposed" returns only the share of each series attributed to one of the run's variants: no "-" remainder is synthesized, and flows never assigned to the experiment are excluded entirely. Requires experimentRunID naming a run that evaluates this series. Defaults to all (remainder included).
all, exposed "exposed"
Renders the series over an explicit cohort of flows instead of the precalculated data (format
idl-<number>, from POST /observe/idLists). The definition's own query is run on demand, scoped to
those flow ids and with NO time predicate — the list is the selection, whenever those flows happened —
so fromDate/toDate only set the (single) bucket the result is stamped with. The panel is otherwise
identical in shape, including groupBy, facets and engagement filtering.
Requires a flow-id capable definition (currently the flow series: login-flow-v1, signup-flow-v1,
security-enrollment-flow-v1, recovery-flow-v1, custom-flow-v1); other definitions return a validation
error. Cannot be combined with experimentRunID. The definition's own scoping still applies, so a list
mixing flow types renders only the flows this series is about.
"idl-123456"
Live recalculation of precalculated data. latest runs the definition's query live for the currently open bucket (the one containing now) instead of reading its stored, possibly stale snapshot; closed buckets keep reading the stored data. Has no effect when the range does not contain the open bucket. Only valid with the hourly interval and cannot be combined with experimentRunID or flowListID.
Which buckets of a precalculated read are computed live instead of read from storage
latest Response
Time series data grouped by time
Show child attributes
Show child attributes
Was this page helpful?