curl --request POST \
--url https://api.cloud.corbado.io/v1/observe/subFlowSearch \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"includeDetails": true,
"userIDs": [
"<string>"
],
"flowIDs": [
"<string>"
],
"sessionIDs": [
"<string>"
],
"subFlowTypes": [],
"limit": 100
}
'import requests
url = "https://api.cloud.corbado.io/v1/observe/subFlowSearch"
payload = {
"includeDetails": True,
"userIDs": ["<string>"],
"flowIDs": ["<string>"],
"sessionIDs": ["<string>"],
"subFlowTypes": [],
"limit": 100
}
headers = {
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
body: JSON.stringify({
includeDetails: true,
userIDs: ['<string>'],
flowIDs: ['<string>'],
sessionIDs: ['<string>'],
subFlowTypes: [],
limit: 100
})
};
fetch('https://api.cloud.corbado.io/v1/observe/subFlowSearch', 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/subFlowSearch",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'includeDetails' => true,
'userIDs' => [
'<string>'
],
'flowIDs' => [
'<string>'
],
'sessionIDs' => [
'<string>'
],
'subFlowTypes' => [
],
'limit' => 100
]),
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>",
"Content-Type: application/json"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://api.cloud.corbado.io/v1/observe/subFlowSearch"
payload := strings.NewReader("{\n \"includeDetails\": true,\n \"userIDs\": [\n \"<string>\"\n ],\n \"flowIDs\": [\n \"<string>\"\n ],\n \"sessionIDs\": [\n \"<string>\"\n ],\n \"subFlowTypes\": [],\n \"limit\": 100\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Authorization", "Bearer <token>")
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://api.cloud.corbado.io/v1/observe/subFlowSearch")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"includeDetails\": true,\n \"userIDs\": [\n \"<string>\"\n ],\n \"flowIDs\": [\n \"<string>\"\n ],\n \"sessionIDs\": [\n \"<string>\"\n ],\n \"subFlowTypes\": [],\n \"limit\": 100\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.cloud.corbado.io/v1/observe/subFlowSearch")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Authorization"] = 'Bearer <token>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"includeDetails\": true,\n \"userIDs\": [\n \"<string>\"\n ],\n \"flowIDs\": [\n \"<string>\"\n ],\n \"sessionIDs\": [\n \"<string>\"\n ],\n \"subFlowTypes\": [],\n \"limit\": 100\n}"
response = http.request(request)
puts response.read_body{
"subFlows": [
{
"id": "<string>",
"flowID": "<string>",
"sessionID": "<string>",
"applicationID": "<string>",
"errorCount": 123,
"completed": true,
"durationMs": 123,
"startMs": 123,
"data": {
"type": "passkeyLogin",
"specType": "<string>",
"configVariantID": "<string>",
"cdaLikelihood": "no"
},
"passkeyID": "<string>",
"clientEnvDataID": "<string>",
"additionalClientEnvDataID": "<string>",
"touchpoint": "<string>",
"bindingID": "<string>",
"previouslySeenBucket": "<string>",
"userID": "<string>",
"clientEnvID": "<string>",
"firstInteractionDurationMs": 123,
"detailedOutcome": "<string>",
"connectedDecisionID": "<string>",
"environment": {
"osName": "<string>",
"browserName": "<string>",
"osVersion": "<string>",
"clientEnvType": "web",
"browserVersion": "<string>",
"deviceModel": "<string>",
"deviceBrand": "<string>",
"deviceOwnerAuth": "<string>",
"playServicesVersion": "<string>"
},
"errors": [
{
"flavourID": "<string>",
"createdMs": 123,
"durationMs": 123
}
],
"customTags": [
{
"name": "<string>",
"value": "<string>"
}
]
}
],
"totalCount": 123,
"flowIDs": [
"<string>"
],
"aggregates": [
{
"values": {},
"count": 123
}
],
"aggregateTruncated": true
}{
"error": {
"message": "Validation failed",
"details": [
{
"field": "projectID",
"message": "required"
}
]
}
}Search subflows by filter criteria
Finds subflows using exactly one selector: userIDs, flowIDs, sessionIDs or filters.
The filters selector searches a time window with optional error, environment and subflow filters.
ID-based results are ordered by start time, oldest first. Filter-based results are newest first
and include totalCount (uncapped matches) and flowIDs (distinct parent flows).
All results are capped at limit.
Required API key permission: observe:subFlows:read.
curl --request POST \
--url https://api.cloud.corbado.io/v1/observe/subFlowSearch \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"includeDetails": true,
"userIDs": [
"<string>"
],
"flowIDs": [
"<string>"
],
"sessionIDs": [
"<string>"
],
"subFlowTypes": [],
"limit": 100
}
'import requests
url = "https://api.cloud.corbado.io/v1/observe/subFlowSearch"
payload = {
"includeDetails": True,
"userIDs": ["<string>"],
"flowIDs": ["<string>"],
"sessionIDs": ["<string>"],
"subFlowTypes": [],
"limit": 100
}
headers = {
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
body: JSON.stringify({
includeDetails: true,
userIDs: ['<string>'],
flowIDs: ['<string>'],
sessionIDs: ['<string>'],
subFlowTypes: [],
limit: 100
})
};
fetch('https://api.cloud.corbado.io/v1/observe/subFlowSearch', 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/subFlowSearch",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'includeDetails' => true,
'userIDs' => [
'<string>'
],
'flowIDs' => [
'<string>'
],
'sessionIDs' => [
'<string>'
],
'subFlowTypes' => [
],
'limit' => 100
]),
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>",
"Content-Type: application/json"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://api.cloud.corbado.io/v1/observe/subFlowSearch"
payload := strings.NewReader("{\n \"includeDetails\": true,\n \"userIDs\": [\n \"<string>\"\n ],\n \"flowIDs\": [\n \"<string>\"\n ],\n \"sessionIDs\": [\n \"<string>\"\n ],\n \"subFlowTypes\": [],\n \"limit\": 100\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Authorization", "Bearer <token>")
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://api.cloud.corbado.io/v1/observe/subFlowSearch")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"includeDetails\": true,\n \"userIDs\": [\n \"<string>\"\n ],\n \"flowIDs\": [\n \"<string>\"\n ],\n \"sessionIDs\": [\n \"<string>\"\n ],\n \"subFlowTypes\": [],\n \"limit\": 100\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.cloud.corbado.io/v1/observe/subFlowSearch")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Authorization"] = 'Bearer <token>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"includeDetails\": true,\n \"userIDs\": [\n \"<string>\"\n ],\n \"flowIDs\": [\n \"<string>\"\n ],\n \"sessionIDs\": [\n \"<string>\"\n ],\n \"subFlowTypes\": [],\n \"limit\": 100\n}"
response = http.request(request)
puts response.read_body{
"subFlows": [
{
"id": "<string>",
"flowID": "<string>",
"sessionID": "<string>",
"applicationID": "<string>",
"errorCount": 123,
"completed": true,
"durationMs": 123,
"startMs": 123,
"data": {
"type": "passkeyLogin",
"specType": "<string>",
"configVariantID": "<string>",
"cdaLikelihood": "no"
},
"passkeyID": "<string>",
"clientEnvDataID": "<string>",
"additionalClientEnvDataID": "<string>",
"touchpoint": "<string>",
"bindingID": "<string>",
"previouslySeenBucket": "<string>",
"userID": "<string>",
"clientEnvID": "<string>",
"firstInteractionDurationMs": 123,
"detailedOutcome": "<string>",
"connectedDecisionID": "<string>",
"environment": {
"osName": "<string>",
"browserName": "<string>",
"osVersion": "<string>",
"clientEnvType": "web",
"browserVersion": "<string>",
"deviceModel": "<string>",
"deviceBrand": "<string>",
"deviceOwnerAuth": "<string>",
"playServicesVersion": "<string>"
},
"errors": [
{
"flavourID": "<string>",
"createdMs": 123,
"durationMs": 123
}
],
"customTags": [
{
"name": "<string>",
"value": "<string>"
}
]
}
],
"totalCount": 123,
"flowIDs": [
"<string>"
],
"aggregates": [
{
"values": {},
"count": 123
}
],
"aggregateTruncated": true
}{
"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.
Body
Include batched environment, error and tag details for userIDs, flowIDs or sessionIDs selectors. Filters retain their output setting.
User IDs to fetch subflows for (format tus-<number>). Mutually exclusive with other selectors.
Flow IDs to fetch subflows for (format flw-<number>). Mutually exclusive with other selectors.
Public session UUIDs to fetch subflows for. Mutually exclusive with other selectors.
Time-window selector with optional narrowing filters. All narrowing filters are AND-combined; values within one filter are OR-combined. Filters targeting a column a subflow type does not have (e.g. cdaLikelihoods on anything but passkey-login) simply exclude that type from the result.
Show child attributes
Show child attributes
Optional subflow types to include.
Observe subflow type.
passkeyLogin, passwordLogin, socialLogin, provideIdentifier, decision, passkeyEnrollment, emailOTP, emailLink, setPassword, provideData, reset, passkeyDeletion, smsOTP, totp, appConfirmation, systemCredential, trustedDeviceCheck, trustedDeviceEnrollment, keySigning, keyRegistration Maximum number of subflows to return in total across all subflow types. Defaults to 100, maximum 10000.
1 <= x <= 10000Response
Matching subflows.
Matching subflows, capped at limit. ID-based selectors order oldest first; the filters selector orders newest first.
Show child attributes
Show child attributes
Total number of matching subflows before the limit cap. Only set for the filters selector — compare against the returned count to detect truncation (analytics over a truncated result cover the most recent totalCount-of-limit slice, not the whole window).
Deduplicated parent flow IDs (format flw-<number>) of the returned subflows, in result order. Only set
for the filters selector. Store via POST /observe/idLists to hand the cohort to user-search or a
funnel.
Per-group counts of the matching subflows — only set for output=aggregate. Ordered by count descending and capped at 5000 cells (aggregateTruncated reports the cap firing; totalCount stays exact regardless). A value of "" means the dimension is not set on the subflow.
Show child attributes
Show child attributes
True when the aggregate cell cap dropped long-tail groups.
Was this page helpful?