Field Names That Differ From the API
Bluejay as Code uses a few friendlier names in the payload than the underlying resource does:
You can also set a Digital Human’s voice with one combined key instead of three.
"accent": "en_american_masculine" follows the pattern language_accent_gender, with gender spelled masculine or feminine. Bluejay splits it into language, accent, and gender on push. Pull always emits the three separate fields, so this shorthand only matters when you’re hand-writing a new Digital Human.
Choosing a Digital Human Type
A Digital Human’stag decides its type. The one exception is customer journeys, which use journey_steps instead. Pick one type per Digital Human. Each example below omits simulations; add it to attach the Digital Human to one, as shown on the walkthrough.
Base human
Base human
Voicemail
Voicemail
Set
tag to voicemail-custom or voicemail-ai-generated.If
tag carries more than one label, put the voicemail tag first. Bluejay only checks the first label.Transcript replay
Transcript replay
formatted_transcript replays a real call instead of description/success_criteria. original_transcript takes raw text instead and costs one LLM call to extract the turns.Customer journey
Customer journey
A customer journey runs multiple calls with the same caller in order.
Scenario Builder
Scenario Builder
A Digital Human tagged
Scenario is generated by Scenario Builder.Don’t write
enriched_playback by hand. It won’t stay in sync with the graph.Red teaming
Red teaming
A Digital Human generated by a red teaming run carries
attack_vector, attack_type, and a much larger attack_plan. Launch these through red teaming, not Bluejay as Code.livekit_metadata is also a generated field: agent connection plumbing set through the agent’s LiveKit connection settings, not written by hand.
Agents, Simulations, and Custom Metrics
These three don’t have archetypes the way Digital Humans do, so there’s one example each instead of an accordion per variant.Agents
Every field here is exactly what add-agent takes.
Simulations
Every field here is exactly what update-simulation takes. A simulation has no
agent field of its own: a new simulation’s owner comes from whichever agent’s simulations array lists its bluejay_as_code_id, or from the bundle’s only agent when there’s just one. An existing simulation’s owner can’t be reassigned on push.Custom Metrics
Every field here is exactly what create-custom-metric takes.
metric_type decides which other fields apply — tool_call and the audio_* types read from settings instead of prompt.Conditional and Composite Metrics
Two kinds of custom metric are configured entirely throughsettings, so they don’t appear in the create-custom-metric field list.
A conditional metric only runs on conversations that match a condition you set. On every other call it comes back not applicable instead of failing, so a metric about insurance doesn’t drag down calls that never discussed insurance.
A composite metric doesn’t judge the conversation at all. It reads the scores of other metrics on the same call and turns them into a number or a label. There’s no LLM call, so it costs nothing to run.
The examples below continue the Cedar Park Family Medicine agent from the walkthrough, and read the two metrics already in that payload plus one more, Scheduling Clarity, a 1-5 score on how clearly the agent handled the booking.
Only run a metric on calls that reached a workflow node
Only run a metric on calls that reached a workflow node
Add Get your agent’s node IDs from
fire_conditions to any metric’s settings. Conditions are grouped: the conditions inside a group are joined by that group’s join, and the groups are joined by the outer one. Both joins default to and, so a single condition needs nothing but groups.This metric only runs on calls that reached the agent’s insurance step.GET /v1/agents/{agent_id}/workflow/summary.A condition’s type is one of three:A
metric condition’s operator is one of equals, not_equals, greater_than, less_than, greater_than_or_equal, less_than_or_equal, contains, or not_contains. You can mix types freely, up to ten conditions across all groups.An older metric you pull might have
settings.fire_node_ids instead — a flat list of node IDs. It still works, but it can’t express groups or the other two condition types. Write fire_conditions for anything new.Combine other metrics into a score
Combine other metrics into a score
Set Each result saves its own arithmetic in
metric_type to composite and output_type to quantitative. List the metrics it reads in variables, then combine them in master_formula.Refer to a metric by its ID in double quotes. That’s the only way to name one, and it means the formula keeps working when someone renames a metric. Formulas are arithmetic only — +, -, *, /, %, **, and the functions min, max, abs, round, floor, and ceil.A metric that isn’t already a number needs a value_map to become one. Here a pass on Appointment Details Confirmed counts as 5 and a fail counts as 1.reasoning, so you can always see where a score came from:Combine other metrics into a label
Combine other metrics into a label
Set A rule can only read a metric that
output_type to categorical and replace the formula with rules. Each rule gives an output and the conditions that produce it, grouped the same way fire_conditions are. Conditions here compare a declared metric against a number, so pass/fail inputs still need a value_map.Rules are checked in order and the first one that holds wins, so put the strictest first. If no rule holds, the metric is not applicable — there’s no fallback label. You get up to ten rules, each with up to ten conditions.variables already lists.output_type or a formula reading a metric that variables doesn’t list, comes back as 200 with valid: false and an errors array, not as a 4xx. Check valid on every push rather than the status code alone.
Wiring them together
A simulation’sdigital_humans, an agent’s custom_metrics, and a custom metric’s agents are all desired-state arrays, not append-only lists. Push replaces each one with exactly what you listed. Drop an id that was there before, and Bluejay unlinks it without deleting the object itself. See reconciliation in the walkthrough for the full behavior.