Skip to main content

Response Recursion

Response recursion lets a single endpoint walk a tree of resources. After the endpoint returns a response (including any paginated pages), Traces extracts child IDs from that response and calls the same endpoint again for each child, substituting the child ID into a URL path parameter. It repeats this for every level of the tree until it runs out of children or hits a depth limit.

Recursion is configured in the same panel as pagination, in the Recursive (Tree Traversal) section. The section only appears when the endpoint has at least one path parameter (e.g. {id}), because recursion needs a parameter to substitute the child ID into.

When to Use

Use recursion when a resource references other resources of the same type, forming a hierarchy that you want to collect in full:

  • Comment / reply trees — a comment endpoint whose response lists child comment IDs.
  • Folder / file hierarchies — a folder endpoint that returns sub-folder IDs.
  • Block / content trees — e.g. Notion's blocks/{id}/children, where some blocks have has_children: true and contain nested blocks.
  • Org charts, category trees, threaded messages — any self-referencing structure where one record points at its children.

If the related records are a different resource type (e.g. users → their posts), use Dependency Mapping instead. Recursion is specifically for an endpoint that calls itself.

How It Works

  1. The endpoint is called normally for the root ID, and pagination (if configured) runs to completion.
  2. Traces extracts child IDs from the response using the ID Path.
  3. If a Condition Path is set, it keeps only the IDs whose condition field equals the Condition Value.
  4. Duplicate IDs within a level are removed.
  5. For each remaining child ID, the URL Parameter is replaced with that ID and the endpoint is called again — one level deeper.
  6. Steps 2–5 repeat for each child until there are no more children or Max Depth is reached.

The root call is depth 1; its direct children are depth 2, and so on. Each collected child response is stamped with __traces.recursionDepth and __traces.parentId so you can see where it came from in the tree.

Configuration Fields

FieldRequiredDescription
ID PathYesJSON path (with [*] wildcard) to the child IDs in the response. The response object is the root. Example: results[*].id.
URL ParameterYesWhich URL path parameter receives each child ID. For /blocks/{id}/children, this is id. Auto-filled when the endpoint has exactly one path parameter.
Condition PathNoJSON path to a boolean field that decides whether to recurse into an item. When empty, every extracted ID is followed. Example: results[*].has_children.
Condition ValueNoThe value the condition field must equal to recurse. Defaults to true. Only shown once a Condition Path is set.
Max DepthYesHow many levels deep the walk may go. Default 5, minimum 1, maximum 10.

Path syntax

ID Path and Condition Path use the same [*] wildcard notation. [*] iterates an array, and the ID Path and Condition Path are evaluated positionally — the first extracted ID is matched against the first condition value, the second against the second, and so on. Keep both paths aligned to the same array so the filter lines up with the right items. For example, results[*].id and results[*].has_children both walk the results array, so each ID is paired with its own has_children flag.

Use the Test button next to ID Path to preview which values the path extracts from the sample response before relying on it.

Worked Example

Collecting a Notion block tree. The endpoint is GET /blocks/{id}/children, and its response looks like:

{
"results": [
{ "id": "block_a", "type": "paragraph", "has_children": false },
{ "id": "block_b", "type": "toggle", "has_children": true },
{ "id": "block_c", "type": "column", "has_children": true }
]
}

Configure recursion so that Traces follows only the blocks that actually have children:

{
"enabled": true,
"idPath": "results[*].id",
"targetParam": "id",
"conditionPath": "results[*].has_children",
"conditionValue": true,
"maxDepth": 5
}

With the root call on block_root:

  • Depth 1: GET /blocks/block_root/children returns the three blocks above.
  • The condition filter keeps block_b and block_c (both has_children: true) and drops block_a.
  • Depth 2: GET /blocks/block_b/children and GET /blocks/block_c/children are called.
  • Each of those responses is processed the same way, descending until no block has children or depth 5 is reached.

Stop and Safety Behaviour

Recursion is bounded so a misconfigured path cannot run away:

  • Max Depth — the walk stops descending once depth exceeds the configured maxDepth. The schema caps this at 10, regardless of what is typed in.
  • Request budget — every walk also has a hard request-count cap independent of depth. A standalone recursion test is limited to 50 requests for the whole tree walk; in iteration test mode each per-ID walk is limited to 20 requests (because budgets multiply across iteration values and IDs). When the budget is reached, the walk stops even if more children remain.
  • Duplicate removal — duplicate child IDs found at the same level are collapsed to a single call.
  • Errors are non-fatal — if a recursive call returns an error or no data, that branch simply stops; the rest of the walk continues.
  • Condition gating — when a Condition Path is set, branches that don't meet the condition are never followed, which is the cleanest way to avoid descending into leaf nodes.

These limits exist primarily to bound the blast radius of an incorrect ID Path (for example, one that re-extracts the parent's own ID and creates a cycle). If a test stops early at the request budget, narrow the recursion with a Condition Path or verify the ID Path with the Test button rather than raising Max Depth.

How It's Stored

Recursion settings live on the endpoint's recursionConfig. In saved test data they are packed into the paginationConfigs JSONB under the __recursionConfigs key, mapped per endpoint ID, and unpacked back into separate fields when the test data is loaded. You configure all of this through the UI — you don't edit the stored JSON directly.

  • Pagination Reference — recursion runs after pagination completes for each call.
  • Dependency Mapping — use this when related records are a different resource type.
  • Test Modes — recursion request budgets differ between standalone and iteration tests.