wldx-admin-cli 1.2.1 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1 @@
1
+ {"openapi":"3.0.2","info":{"title":"wilma","sources":["https://docs.wildix.com/codegen/api/wilma-agents-client.openapi.json","https://docs.wildix.com/codegen/api/wilma-artifacts-client.openapi.json","https://docs.wildix.com/codegen/api/wilma-assistant-client.openapi.json","https://docs.wildix.com/codegen/api/wilma-audit-client.openapi.json","https://docs.wildix.com/codegen/api/wilma-copilot-client.openapi.json","https://docs.wildix.com/codegen/api/wilma-copilot-sessions-client.openapi.json","https://docs.wildix.com/codegen/api/wilma-sandbox-client.openapi.json","https://docs.wildix.com/codegen/api/wilma-secrets-client.openapi.json","https://docs.wildix.com/codegen/api/wilma-skills-client.openapi.json","https://docs.wildix.com/codegen/api/wilma-tools-client.openapi.json","https://docs.wildix.com/codegen/api/wilma-traces-client.openapi.json","https://docs.wildix.com/codegen/api/wilma-voice-client.openapi.json"]},"servers":[{"url":"https://api.wilma.wildix.com","description":"Production"},{"url":"https://api-stable.wilma.wildix.com","description":"Stable"},{"url":"https://api-stage.wilma.wildix.com","description":"Stage"}],"paths":{"/v2/agents/agents":{"get":{"operationId":"ListAgents","summary":"","description":"Retrieves a paginated list of agents for the specified company.\n\n### Allowed Auth Scopes\n\n- wilma-agents:ListAgents\n- wilma-agents:read","parameters":[{"name":"company","in":"query","required":false,"schema":{"type":"string","maxLength":30,"minLength":3}},{"name":"channel","in":"query","required":false,"schema":{"type":"string","description":"A channel an agent can serve (see AgentChannels for enablement). Also the runtime channel recorded by the channel runtimes / wilma-traces. A background task has no delivery surface and therefore no channel.","enum":["voice","chat","assistant"]},"description":"Only agents that have this channel enabled (voice / chat / assistant) and whose engine can\nserve it. An agent serving no channel matches no value of this filter, so a caller listing\nagents to run as a task must leave it unset."},{"name":"status","in":"query","required":false,"schema":{"type":"string","enum":["draft","active"]}},{"name":"search","in":"query","required":false,"schema":{"type":"string","description":"Filter by name or category."},"description":"Filter by name or category."},{"name":"limit","in":"query","required":false,"schema":{"type":"number","maximum":100,"minimum":1}},{"name":"cursor","in":"query","required":false,"schema":{"type":"string","maxLength":2048,"minLength":1,"description":"Opaque cursor returned by a previous page; pass it back to fetch the next page."},"description":"Opaque cursor returned by a previous page; pass it back to fetch the next page."}]},"post":{"operationId":"CreateAgent","summary":"","description":"Creates a new mutable agent draft. The agent serves no traffic until the draft is published and PutAgentDeployment routes sessions to that version.\n\n### Allowed Auth Scopes\n\n- wilma-agents:CreateAgent","parameters":[{"name":"company","in":"query","required":false,"schema":{"type":"string","maxLength":30,"minLength":3}}],"requestBody":{"required":true,"schema":{"type":"object","description":"The complete, versionable configuration of an agent. This is what gets snapshotted when a version is published.","properties":{"name":{"type":"string","maxLength":255,"minLength":3},"description":{"type":"string","maxLength":1000,"minLength":1},"category":{"type":"string","maxLength":128,"minLength":1,"description":"Category of the agent, e.g.: 'Support'."},"picture":{"type":"string","description":"Avatar URL."},"channels":{"type":"object","description":"The channels an agent serves and their channel-specific settings. Presence of a member enables that channel. Empty means the agent serves no channel: it is not reachable on its own and runs only as an automation's task or when another agent calls it. Enabled channels are mirrored as ChannelType values on AgentInfo.","properties":{"voice":{"type":"object","description":"Voice channel settings. Note: a voice agent's AgentAccess.grants are ignored — inbound calls are routed by the PBX dialplan and data ACL is the agent-level `settings.pbxGroupId`, not per-user grants.","properties":{"greeting":{"type":"string","description":"Message spoken when the call starts."},"backgroundSound":{"type":"object","description":"Ambient background audio played under the call.\n\n A SETTINGS OBJECT like `interruptions` and `endCall`, rather than a loose name on the channel:\n it is one switchable call behaviour, it reads the same way as its siblings, and the volume and\n fade controls the console will want next have somewhere to go.\n\n ABSENT MEANS OFF — a bot that never touches it behaves exactly as every call did before this\n setting existed, and the PBX needs no new case.","properties":{"enabled":{"type":"boolean","description":"Whether ambient background audio plays under the call."},"sound":{"type":"string","maxLength":64,"minLength":1,"pattern":"^[^\\r\\n]+$","description":"Music-on-hold class the PBX plays. Absent with `enabled` true leaves the choice of class to the PBX."}},"required":["enabled"]},"speech":{"type":"object","description":"Speech configuration for the voice channel. When present it WINS over the dialplan's `voice` and\n `initialMessage` connect parameters; `language` is the single exception, because it is the\n channel's own input signal and the feature has no starting point without it.","properties":{"languages":{"type":"array","items":{"type":"object","description":"One configured language: its voice, its initial phrase and its pronunciation rules.","properties":{"code":{"type":"string","maxLength":8,"minLength":2,"pattern":"^[a-z]{2,3}(-[A-Z]{2})?$","description":"BCP-47 code with locale, e.g. 'en-US', 'it-IT'."},"voice":{"type":"string","maxLength":1024,"minLength":1,"pattern":"^[a-z0-9][a-z0-9_-]*://[a-zA-Z0-9._~/-]+(\\?[a-zA-Z0-9._~%&=+:/-]*)?$","description":"Voice this language speaks with, as a URI — provider, voice id, synthesis parameters and,\n where the company uses its own provider account, an `apiKey=secrets:{secretId}` reference.\n See VoiceUri.\n\n PER LANGUAGE, and that is the whole reason the member is here rather than on the settings\n above: provider quality is not uniform across languages, a voice id belongs to exactly one\n provider, and a bot-wide provider made changing it invalidate every configured voice at\n once."},"greeting":{"type":"string","description":"Initial phrase spoken when the call starts in this language. Absent or empty means\n the agent says nothing and waits — it does NOT fall back to VoiceChannelSettings.greeting,\n which would speak one language's text in another language's voice."},"isDefault":{"type":"boolean","description":"Marks the language a caller hears when their own matches nothing configured.\n\n THE FULL RESOLUTION ORDER, since a client author reading only this contract cannot\n otherwise find it: an exact match on the call's language, then a match on its primary\n subtag (`fr` finds `fr-FR`), then the language marked here, then a language whose\n primary subtag is `en`, then the first entry in the list.\n\n Marked is consulted BEFORE the English step, so a record with none marked resolves\n exactly as it did before this member existed. English is deliberate rather than\n arbitrary — it is the language a caller the agent was not built for is likeliest to\n understand — and the last step exists only to keep the rule total, which is why list\n order should not be treated as configuration.\n\n At most one language may carry it. A record with two — reachable through the API or\n the CLI, where no form prevents it — resolves to the first marked one in list order,\n because refusing a call over configuration drift is worse than serving it and saying\n so in the trace."},"pronunciations":{"type":"array","items":{"type":"object","description":"A pronunciation rule for one phrase. Belongs to a LANGUAGE, not to the agent: 'Wildix' is\n respelled differently for an Italian and a German voice, so one list shared ACROSS languages\n would fight itself. That is why the rules hang off the language and nowhere else — there is no\n company-wide dictionary above them to inherit from, or to disagree with.","properties":{"phrase":{"type":"string","maxLength":200,"description":"The phrase as it appears in the text, e.g. 'Wildix'. Bounded because every rule is compiled into ONE regular expression — see the list below."},"as":{"type":"string","maxLength":200,"description":"Respelling (ALIAS) or phonetic transcription (IPA), e.g. 'Wil-dix'."},"encoding":{"type":"string","description":"How the `as` member of a pronunciation rule is written.","enum":["alias","ipa"]}},"required":["as","phrase"]},"maxItems":200,"description":"The rules this language is spoken with. THE WHOLE SET rather than an override layer: an\n agent's respellings are stored here and read from here, so what this list says is what the\n caller hears.\n\n THERE IS NOTHING ABOVE IT. An earlier draft of this contract carried a company-wide\n dictionary per language that every agent speaking it inherited, with a merge order over this\n member and an identity rule ('Wildix' -> 'Wildix') for declining an inherited respelling.\n It bought correcting a handful of names in one place, and cost a second store, a merge on\n every synthesis path, a screen of its own, and a reader who could not tell from an agent\n what that agent would actually say. Per language per agent is where the divergence that\n matters already lives."}},"required":["code","voice"]},"maxItems":10,"minItems":1,"description":"Configured languages. The @length bound is a STORAGE ceiling, not a promise about what a\n recogniser accepts at once: automatic detection here is opt-in (`transcription.autoDetect`),\n and once a caller turns it on, the service does not clamp the resulting candidate list to a\n recogniser's own practical limit today (Amazon Transcribe's documented recommendation for\n streaming identification is five) — a caller who both opts in and configures more than a\n recogniser accepts should expect degraded identification or a rejected stream, not a refusal\n from this contract. With detection off, only the single resolved language is ever sent, so\n the ceiling never becomes a candidate-list problem at all."},"greetEveryLanguage":{"type":"boolean","description":"Speak the first message of every configured language in turn when the call is answered, each\n in the voice, provider account and pronunciation rules of its own language, instead of only the\n language the call resolved to. Languages whose first message is empty, and languages the PBX\n synthesizes itself, are skipped. Absent means off. Requires at least two languages carrying a\n first message; below that the runtime speaks only the resolved language's phrase, and the\n console clamps the member on save."},"transcription":{"type":"object","description":"Speech-to-text configuration: which recogniser listens to the caller, and whether it identifies the language while it does.","properties":{"engine":{"type":"string","maxLength":1024,"minLength":1,"pattern":"^[a-z0-9][a-z0-9_-]*://[a-zA-Z0-9._~/-]+(\\?[a-zA-Z0-9._~%&=+:/-]*)?$","description":"Recogniser to transcribe the caller with. Absent resolves to the platform default ('aws://transcribe' today), resolved by the server so exactly one component decides it."},"autoDetect":{"type":"boolean","description":"Identify the caller's language per utterance and switch voice and reply language mid-call.\n\n HERE RATHER THAN ON `VoiceSpeechSettings`, because identifying a language is something the\n RECOGNISER does: it becomes identify-multiple-languages plus language-options on the\n recognition stream, and nothing about synthesis reads it.\n\n Requires at least two configured languages and a recogniser that can identify them; the\n server clamps it to false otherwise, because forwarding true with a single candidate makes\n Amazon reject the stream outright."}}}},"required":["languages"]},"interruptions":{"type":"object","properties":{"enabled":{"type":"boolean","description":"Whether the caller can interrupt the agent while it is speaking."},"minWords":{"type":"number","maximum":10,"minimum":1,"description":"Minimum number of caller words that count as an interruption."}},"required":["enabled"]},"silenceTimeout":{"type":"object","properties":{"seconds":{"type":"number","maximum":600,"minimum":1,"description":"Seconds of caller silence before the action is taken."},"action":{"oneOf":[{"type":"object","title":"hangup","properties":{"hangup":{"type":"object","properties":{"message":{"type":"string","description":"Message played before hanging up. Played in every language of the call while `messages` is absent or empty; not consulted once `messages` carries any entry."},"messages":{"type":"array","items":{"type":"object","description":"One phrase in one language, for a capability that speaks a fixed line on the voice channel.\n Which entry is spoken is decided by the call's current language — the one the call resolved\n to at connect, or the one automatic detection switched to — matched on `language`\n CASE-INSENSITIVELY, the same rule `VoiceBotSpokenPhrase` states for the legacy stack, so a\n code written in either case reaches the same entry on either. Blank `text` means the agent\n says nothing in that language; it does NOT fall back to the language-agnostic member beside\n the list, which would speak one language's text in another language's voice.","properties":{"language":{"type":"string","maxLength":8,"minLength":2,"pattern":"^[a-z]{2,3}(-[A-Z]{2})?$","description":"BCP-47 language tag, optionally with a region subtag: 'en', 'en-US', 'pt-BR'. Deliberately a\n constrained string rather than an enum: the offerable set is a property of the selected\n recogniser and changes with it, so an enumeration here would cap the set at whatever was known\n when the model was released."},"text":{"type":"string"}},"required":["language","text"]},"description":"Per-language messages played before hanging up. Same rule as `AgentTool.startMessages`: the entry for the call's current language is played, a language with no entry is silent, and with no effective speech configuration `message` is played."}}}},"required":["hangup"]},{"type":"object","title":"transfer","properties":{"transfer":{"type":"object","properties":{"name":{"type":"string","description":"Human readable name the model can refer to, e.g. 'Billing department'."},"description":{"type":"string","description":"When this target should be chosen."},"context":{"type":"string"},"extension":{"type":"string"}},"required":["context","extension","name"]}},"required":["transfer"]}]}},"required":["action","seconds"]},"maxDuration":{"type":"object","properties":{"seconds":{"type":"number","maximum":14400,"minimum":10,"description":"Maximum call duration in seconds before the action is taken."},"action":{"oneOf":[{"type":"object","title":"hangup","properties":{"hangup":{"type":"object","properties":{"message":{"type":"string","description":"Message played before hanging up. Played in every language of the call while `messages` is absent or empty; not consulted once `messages` carries any entry."},"messages":{"type":"array","items":{"type":"object","description":"One phrase in one language, for a capability that speaks a fixed line on the voice channel.\n Which entry is spoken is decided by the call's current language — the one the call resolved\n to at connect, or the one automatic detection switched to — matched on `language`\n CASE-INSENSITIVELY, the same rule `VoiceBotSpokenPhrase` states for the legacy stack, so a\n code written in either case reaches the same entry on either. Blank `text` means the agent\n says nothing in that language; it does NOT fall back to the language-agnostic member beside\n the list, which would speak one language's text in another language's voice.","properties":{"language":{"type":"string","maxLength":8,"minLength":2,"pattern":"^[a-z]{2,3}(-[A-Z]{2})?$","description":"BCP-47 language tag, optionally with a region subtag: 'en', 'en-US', 'pt-BR'. Deliberately a\n constrained string rather than an enum: the offerable set is a property of the selected\n recogniser and changes with it, so an enumeration here would cap the set at whatever was known\n when the model was released."},"text":{"type":"string"}},"required":["language","text"]},"description":"Per-language messages played before hanging up. Same rule as `AgentTool.startMessages`: the entry for the call's current language is played, a language with no entry is silent, and with no effective speech configuration `message` is played."}}}},"required":["hangup"]},{"type":"object","title":"transfer","properties":{"transfer":{"type":"object","properties":{"name":{"type":"string","description":"Human readable name the model can refer to, e.g. 'Billing department'."},"description":{"type":"string","description":"When this target should be chosen."},"context":{"type":"string"},"extension":{"type":"string"}},"required":["context","extension","name"]}},"required":["transfer"]}]}},"required":["action","seconds"]},"endCall":{"type":"object","properties":{"enabled":{"type":"boolean"},"phrases":{"type":"string","description":"Comma-separated phrases that end the call, e.g. 'goodbye,bye'."}},"required":["enabled"]},"callerMetadata":{"type":"object","description":"Caller context injected into the model prompt.","properties":{"callerName":{"type":"boolean","description":"Include the caller name. Defaults to true."},"callerNumber":{"type":"boolean","description":"Include the caller phone number. Defaults to true."},"callerEmail":{"type":"boolean","description":"Include the caller email when known. Defaults to false."},"callerCompany":{"type":"boolean","description":"Include the caller company when known. Defaults to false."},"time":{"type":"boolean","description":"Include the current time, rendered in the agent's `engine.ai.timeZone`. Defaults to false."}}}}},"chat":{"type":"object","properties":{"discoverable":{"type":"boolean","description":"Whether users can find and add this agent to conversations."},"sessionTimeoutMinutes":{"type":"number","maximum":120,"minimum":15,"description":"Minutes a conversation can stay idle before the next message starts a new session. Omit to start a new session at the end of the agent's local day (`engine.ai.timeZone`). Allowed values: 15, 30, 60, 120."},"verbosity":{"type":"string","description":"How much of a chat turn x-bees shows while the agent works. SMS and WhatsApp always send one final text.","enum":["none","simple","full"]}}},"assistant":{"type":"object","description":"Assistant channel settings. Presence enables the in-app assistant surface; audio remains unavailable unless its separate settings are present and enabled.","properties":{"default":{"type":"boolean","description":"Offer this agent as the default assistant for users covered by the agent access settings."},"ui":{"type":"object","description":"How the agent presents itself in the assistant UI: what the user reads before typing anything, and what they can start from without typing at all. Everything here is presentation — it never changes what the agent can do.","properties":{"welcome":{"type":"object","description":"Greeting shown on an empty thread, above the quick commands.","properties":{"title":{"type":"string","maxLength":80,"minLength":1,"description":"Heading, e.g. 'How can I help?'. Rendered on one line."},"subtitle":{"type":"string","maxLength":200,"minLength":1,"description":"Line under the heading, saying what this agent is for, e.g. 'Ask about your conversations, accounts and calls.'."}}},"commands":{"type":"array","items":{"type":"object","description":"A starter prompt offered as a card on an empty thread. Picking it sends `prompt` as the user's first message, so the user never types it.","properties":{"title":{"type":"string","maxLength":60,"minLength":1,"description":"Card label, e.g. 'Summarize my day'. Rendered on one line and truncated when it does not fit."},"description":{"type":"string","maxLength":120,"minLength":1,"description":"Optional line under the label. Also one line, so keep it to a short clarification of what the command does."},"prompt":{"type":"string","maxLength":2000,"minLength":1,"description":"The message actually sent when the card is picked. Usually longer and more explicit than the label, since it is what the model reads."}},"required":["prompt","title"]},"maxItems":8,"description":"Cards offered under the welcome message on an empty thread, in the order given. Empty or omitted shows no cards."},"placeholder":{"type":"string","maxLength":100,"minLength":1,"description":"Placeholder text in the composer, e.g. 'Ask about an account…'. Defaults to the client's own placeholder when omitted."}}},"audio":{"type":"object","description":"Switchable audio for the assistant channel. The settings object keeps enablement beside its\n speech policy, matching the voice channel's switchable blocks so a disabled configuration can\n be retained without serving audio.","properties":{"enabled":{"type":"boolean","description":"Whether the assistant accepts audio calls. Required so the presence of saved speech configuration is never mistaken for enablement."},"speech":{"type":"object","description":"Recognition and synthesis policy for assistant audio. This is assistant-owned rather than a\n VoiceSpeechSettings reuse so telephony and assistant audio can evolve independently. It has no\n language greeting and no greet-every-language switch because an assistant conversation is\n already open when its audio call starts, leaving nothing for the channel to greet.","properties":{"languages":{"type":"array","items":{"type":"object","description":"One language the assistant audio channel can recognise and speak. There is deliberately no\n greeting here: an assistant conversation is already open when its audio call starts, so there\n is no new conversation to welcome and no initial phrase for the runtime to choose or repeat.","properties":{"code":{"type":"string","maxLength":8,"minLength":2,"pattern":"^[a-z]{2,3}(-[A-Z]{2})?$","description":"BCP-47 code with locale, e.g. 'en-US', 'it-IT'. Reuses the channel-neutral language value so assistant and voice configuration accept the same codes without sharing policy structures."},"voice":{"type":"string","maxLength":1024,"minLength":1,"pattern":"^[a-z0-9][a-z0-9_-]*://[a-zA-Z0-9._~/-]+(\\?[a-zA-Z0-9._~%&=+:/-]*)?$","description":"Voice this language speaks with. Reuses the channel-neutral URI value so providers and synthesis parameters have the same representation in every channel."},"isDefault":{"type":"boolean","description":"Marks the language used when the caller's language matches nothing configured.\n\n THE FULL RESOLUTION ORDER, because a client author reading only this contract cannot\n otherwise find it: an exact match on the call's language, then a match on its primary\n subtag (`fr` finds `fr-FR`), then the language marked here, then a language whose primary\n subtag is `en`, then the first entry in the list. The same order the voice channel resolves\n by — the rule belongs to choosing a language, not to a channel.\n\n At most one language may carry it. A record with two — reachable through the API, where no\n form prevents it — resolves to the first marked one in list order: refusing an\n already-open assistant conversation over configuration drift is worse than serving it."},"pronunciations":{"type":"array","items":{"type":"object","description":"A pronunciation rule for one phrase. Belongs to a LANGUAGE, not to the agent: 'Wildix' is\n respelled differently for an Italian and a German voice, so one list shared ACROSS languages\n would fight itself. That is why the rules hang off the language and nowhere else — there is no\n company-wide dictionary above them to inherit from, or to disagree with.","properties":{"phrase":{"type":"string","maxLength":200,"description":"The phrase as it appears in the text, e.g. 'Wildix'. Bounded because every rule is compiled into ONE regular expression — see the list below."},"as":{"type":"string","maxLength":200,"description":"Respelling (ALIAS) or phonetic transcription (IPA), e.g. 'Wil-dix'."},"encoding":{"type":"string","description":"How the `as` member of a pronunciation rule is written.","enum":["alias","ipa"]}},"required":["as","phrase"]},"maxItems":200,"description":"The rules this language is spoken with, same shape and same per-language ownership as\n VoiceLanguage.pronunciations — a respelling is about how a synthesized reply SOUNDS, not\n about how a conversation starts, so the rationale that leaves `greeting` off this structure\n does not apply here. Absent means no respellings for this language, distinct from the voice\n channel's OWN configured rules for the same language: an agent's assistant audio does not\n inherit them, so a name gets fixed once and read once, on whichever channel it is saved."}},"required":["code","voice"]},"maxItems":10,"minItems":1,"description":"Languages the assistant audio channel can recognise and speak. The @length bound is a\n STORAGE ceiling, not a promise about what a recogniser accepts at once — unlike the voice\n channel, where a caller opts into identification with `autoDetect`, THIS channel derives\n identification from `languages` and turns it on whenever the recogniser can identify among\n more than one, so every one of the up to ten rows here can become a live candidate list. The\n service does not clamp that list to a recogniser's own practical limit today (Amazon\n Transcribe's documented recommendation for streaming identification is five); a caller\n configuring more than a recogniser accepts should expect degraded identification or a\n rejected stream, not a refusal from this contract."},"transcription":{"type":"object","description":"Speech-to-text configuration owned by the assistant channel, so changes to assistant\n recognition policy cannot alter telephony calls.\n\n NO `autoDetect` HERE, unlike the voice channel: on this channel configuring a second language\n IS the request to recognise which one is being spoken — nobody lists two and then asks to be\n heard in only one — so the server derives identification from the language list and clamps it\n to false when the recogniser cannot identify among them, or there are too few to identify. The\n voice channel keeps the switch because a dialplan can hand a call its language, which is a\n choice an assistant conversation never has to make.","properties":{"engine":{"type":"string","maxLength":1024,"minLength":1,"pattern":"^[a-z0-9][a-z0-9_-]*://[a-zA-Z0-9._~/-]+(\\?[a-zA-Z0-9._~%&=+:/-]*)?$","description":"Recogniser used for assistant audio. Absent resolves to the platform default, resolved by the server so exactly one component decides it."}}}},"required":["languages"]}},"required":["enabled"]}}}}},"engine":{"description":"How the agent produces replies. `ai` is the managed engine; `webhook` and `sqs` delegate to an external backend.","oneOf":[{"type":"object","title":"ai","properties":{"ai":{"type":"object","description":"Managed AI engine: the platform runs the model, tools and knowledge lookups.","properties":{"instructions":{"type":"string","maxLength":60000,"minLength":1},"workflow":{"type":"object","description":"The agent's conversation as a graph: which step runs first, what each step does, and on what condition\n control moves on. Optional on AgentAiEngine — without one the agent is prompt-driven, steered by\n `instructions` and free to use its capabilities in any order.\n\n Versioned with the rest of the agent definition: publishing a version snapshots the graph, and a draft edit\n reaches no session until it is published and routed.","properties":{"entry":{"type":"string","maxLength":64,"minLength":1,"pattern":"^[a-zA-Z0-9_-]+$","description":"Step the graph starts at. Must be one of `steps`."},"steps":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","maxLength":64,"minLength":1,"pattern":"^[a-zA-Z0-9_-]+$","description":"Identifier of a step within its workflow, unique per workflow (the main graph and each nested workflow are separate scopes). Stable and machine-facing: transitions reference it, and a result step's id is the name a caller branches on, so renaming a step's `label` never unwires the graph."},"label":{"type":"string","maxLength":128,"minLength":1,"description":"Human readable name of a step, e.g. 'Transfer to the sales desk'. Shown in the builder and in traces, and\n read by the model too: when this step is an exit of the live step, the label is the preview offered alongside\n the exit's condition. Never a routing key — see AgentWorkflowStepId — so renaming one never unwires a graph."},"stage":{"type":"string","maxLength":64,"minLength":1,"description":"The phase this step belongs to. Inherited by steps downstream, so a handful of tags organise a long graph."},"handler":{"description":"What a step does. Exactly one member is set. `action` carries a capability; the others are workflow-only handlers with no capability equivalent.","oneOf":[{"type":"object","title":"conversation","properties":{"conversation":{"type":"object","description":"A stretch of conversation with a goal: the model talks, may use the capabilities in scope, and the step's\n transitions are evaluated against what happens. This is the only handler that leaves control where it is\n until a transition matches — a step whose conditions all stay false simply keeps talking.\n\n Deliberately not configurable per step: the model, and how readily the step is left. The model is the\n agent's (`engine.ai.model`) because a session that swaps models mid-conversation swaps voice, latency and\n tool-calling behaviour with it. How readily a step is left is decided by how its transitions are worded —\n conditions describing what the *caller* has done keep a model on the step, and no eagerness dial moves that\n number.","properties":{"goal":{"type":"string","maxLength":10000,"minLength":1,"description":"What the agent is trying to achieve here, appended to the agent's `engine.ai.instructions`. Never a replacement for them: identity, tone and standing rules hold across every step, and a step that needs different ones is a different agent."},"entry":{"type":"string","description":"How a conversation step starts.","enum":["auto","speak_first","listen_first"]},"channels":{"type":"object","description":"Per-channel settings of a conversation step. Only voice has anything to say about a stretch of conversation today; the chat and assistant surfaces deliver it as they deliver every other reply.","properties":{"voice":{"type":"object","description":"Voice-channel settings for a conversation step, overriding VoiceChannelSettings while it is live. Ignored on other channels.","properties":{"interruptions":{"type":"object"}}}}},"capabilities":{"type":"array","items":{"oneOf":[{"type":"object","title":"tool","properties":{"tool":{"type":"object","properties":{"id":{"type":"string","maxLength":12,"minLength":12,"pattern":"^[0-9A-Za-z]{12}$","description":"Unique identifier of the tool using nanoid format"},"toolVersion":{"type":"number","minimum":1,"description":"Pin a specific published tool version. When omitted, the tool's latestVersion is resolved at load, so a republished tool reaches this agent without a new agent version. Pin it when `fields` must keep aiming at an exact input schema."},"description":{"type":"string","maxLength":4096,"minLength":1,"description":"Override the tool's description for this agent. When omitted, the tool's own description is used."},"fields":{"type":"array","items":{"type":"object"},"description":"Per-field overrides on the tool input schema: pin fixed values (hidden from the model) or refine field descriptions. Fields not listed are filled by the model per the tool schema. Paths are resolved against the input schema of the tool version in effect — pin `toolVersion` when they must not move."},"startMessage":{"type":"string","description":"Message spoken when the tool call starts. Honoured on the voice channel. Spoken in every language of the call while `startMessages` is absent or empty; not consulted once `startMessages` carries any entry."},"startMessages":{"type":"array","items":{"type":"object"},"description":"Per-language start messages. When present and non-empty, the entry for the call's current\n language is spoken and `startMessage` is not consulted; a language with no entry is silent.\n When absent or empty, `startMessage` is spoken as before. On a call with no effective speech\n configuration — no configured languages, or a PBX below protocol 2 — `startMessage` is spoken\n regardless, because there is no current language to choose by."},"pipeline":{"type":"object"},"approval":{"type":"object"},"channels":{"type":"array","items":{"type":"object"},"uniqueItems":true,"description":"Channels this capability is offered on. Absent or empty — the default — means every channel the agent serves\n that can honour it. Listing channels narrows that set and never widens it: a `transfer` listed for `chat` is\n still ignored there, because the chat channel has no call to transfer."},"annotations":{"type":"array","items":{"type":"object"}}},"required":["id"]}},"required":["tool"]},{"type":"object","title":"connector","properties":{"connector":{"type":"object","description":"A configured connector (wilma-tools) exposed to the model as a single meta-tool named\n after the connector. The model calls the reserved `help` action to learn the callable\n actions, then dispatches an action through the same tool. Progressive disclosure runs\n through the connector tool itself, not the skills catalogue.","properties":{"id":{"type":"string","maxLength":12,"minLength":12,"pattern":"^[0-9A-Za-z]{12}$","description":"Configured connector instance in wilma-tools. Its pinned definition, permission `grant` and authorization govern what the model can discover via `help` and call. There is no version pin here, unlike `AgentTool.toolVersion`: the action catalogue and its schemas are already frozen by the connector's pinned definition, while `grant` and authorization are read live so permission changes apply at once."},"startMessage":{"type":"string","description":"Message spoken when a connector action call starts. Honoured on the voice channel. Mirrors `AgentTool.startMessage`. Not applied to the read-only `help` action. Not consulted once `startMessages` carries any entry."},"startMessages":{"type":"array","items":{"type":"object"},"description":"Forward-compatible per-language connector start messages, mirroring `AgentTool.startMessages`. Accepted and stored so clients can configure connector and tool phrases consistently, but currently ignored at execution time because the voice runtime does not read connector start messages yet. Runtime enablement is tracked in wilma-services docs/todo.md item 8."},"pipeline":{"type":"object"},"approval":{"type":"object"},"channels":{"type":"array","items":{"type":"object"},"uniqueItems":true,"description":"Channels this capability is offered on. Absent or empty — the default — means every channel the agent serves\n that can honour it. Listing channels narrows that set and never widens it: a `transfer` listed for `chat` is\n still ignored there, because the chat channel has no call to transfer."},"annotations":{"type":"array","items":{"type":"object"}}},"required":["id"]}},"required":["connector"]},{"type":"object","title":"kb","properties":{"kb":{"type":"object","properties":{"knowledgeBaseId":{"type":"string"},"instructions":{"type":"string","maxLength":60000,"minLength":1,"description":"Additional instructions for when the agent should use this knowledge base."},"searchStrategy":{"type":"object"},"searchThreshold":{"type":"object"},"searchLimit":{"type":"number","maximum":50,"minimum":1,"description":"Maximum number of passages this capability returns. Defaults to the knowledge base service's own default (10) when omitted. Keep it low on voice: every passage is prompt the model reads before it can speak."},"channels":{"type":"array","items":{"type":"object"},"uniqueItems":true,"description":"Channels this capability is offered on. Absent or empty — the default — means every channel the agent serves\n that can honour it. Listing channels narrows that set and never widens it: a `transfer` listed for `chat` is\n still ignored there, because the chat channel has no call to transfer."},"annotations":{"type":"array","items":{"type":"object"}}}}},"required":["kb"]},{"type":"object","title":"sandbox","properties":{"sandbox":{"description":"Embedded sandbox: shell + file tools backed by an execution environment. The only capability with no `channels` list — being a union it has nowhere to carry one — so it is offered on every channel the agent serves.","oneOf":[{"type":"object","title":"inline","properties":{"inline":{"type":"object"}},"required":["inline"]},{"type":"object","title":"snapshot","properties":{"snapshot":{"type":"object"}},"required":["snapshot"]}]}},"required":["sandbox"]},{"type":"object","title":"webSearch","properties":{"webSearch":{"type":"object","description":"Model-native web search. The provider tool is selected from the agent model.","properties":{"contextSize":{"type":"object"},"allowedDomains":{"type":"array","items":{"type":"string","maxLength":253,"minLength":1},"description":"Restrict results to these domains where the provider supports it."},"userLocation":{"type":"object"},"channels":{"type":"array","items":{"type":"object"},"uniqueItems":true,"description":"Channels this capability is offered on. Absent or empty — the default — means every channel the agent serves\n that can honour it. Listing channels narrows that set and never widens it: a `transfer` listed for `chat` is\n still ignored there, because the chat channel has no call to transfer."}}}},"required":["webSearch"]},{"type":"object","title":"webFetch","properties":{"webFetch":{"type":"object","description":"Server-side fetch of a URL, returning its readable text/markdown to the model.","properties":{"allowedDomains":{"type":"array","items":{"type":"string","maxLength":253,"minLength":1},"description":"Restrict fetches to these domains. Empty list means any domain is allowed."},"channels":{"type":"array","items":{"type":"object"},"uniqueItems":true,"description":"Channels this capability is offered on. Absent or empty — the default — means every channel the agent serves\n that can honour it. Listing channels narrows that set and never widens it: a `transfer` listed for `chat` is\n still ignored there, because the chat channel has no call to transfer."}}}},"required":["webFetch"]},{"type":"object","title":"agent","properties":{"agent":{"type":"object","description":"Invoke another agent as a dedicated execution with its own state. The called agent runs independently and returns its result.","properties":{"agentId":{"type":"string","maxLength":12,"minLength":12},"agentVersion":{"type":"number","minimum":1,"description":"Pin a specific published version of the called agent. When absent, the called agent's deployment configuration selects the version at invocation time."},"access":{"type":"object"},"variables":{"type":"array","items":{"type":"object"}},"channels":{"type":"array","items":{"type":"object"},"uniqueItems":true,"description":"Channels this capability is offered on. Absent or empty — the default — means every channel the agent serves\n that can honour it. Listing channels narrows that set and never widens it: a `transfer` listed for `chat` is\n still ignored there, because the chat channel has no call to transfer."}},"required":["agentId"]}},"required":["agent"]},{"type":"object","title":"handover","properties":{"handover":{"type":"object","description":"Hand the conversation over to a human and leave it. Honoured on conversation channels (chat, assistant).","properties":{"name":{"type":"string","description":"Tool name exposed to the model. Auto-generated when omitted."},"description":{"type":"string","description":"Description for the model to decide when to hand over."},"variant":{"type":"object"},"agent":{"type":"string","maxLength":12,"minLength":12,"description":"Wilma agent to hand over to when variant = AGENT."},"agentVersion":{"type":"number","minimum":1,"description":"Pin a specific published version of the target agent when variant = AGENT. When absent, the target's deployment configuration selects the version at handover time."},"service":{"type":"string","description":"x-bees service id to hand over to when variant = SERVICE."},"silent":{"type":"boolean","description":"Hand over without posting a visible message to the user."},"pipeline":{"type":"object"},"channels":{"type":"array","items":{"type":"object"},"uniqueItems":true,"description":"Channels this capability is offered on. Absent or empty — the default — means every channel the agent serves\n that can honour it. Listing channels narrows that set and never widens it: a `transfer` listed for `chat` is\n still ignored there, because the chat channel has no call to transfer."},"annotations":{"type":"array","items":{"type":"object"}}}}},"required":["handover"]},{"type":"object","title":"delegation","properties":{"delegation":{"type":"object","description":"Delegate the conversation to another agent (max 3 transitions per turn). One capability entry per delegate target.","properties":{"name":{"type":"string","description":"Tool name exposed to the model. Auto-generated when omitted."},"description":{"type":"string","description":"Description for the model to decide when to delegate."},"id":{"type":"string","maxLength":12,"minLength":12,"description":"Agent to delegate to."},"agentVersion":{"type":"number","minimum":1,"description":"Pin a specific published version of the delegate agent. When absent, the delegate's deployment configuration selects the version at delegation time."},"channels":{"type":"array","items":{"type":"object"},"uniqueItems":true,"description":"Channels this capability is offered on. Absent or empty — the default — means every channel the agent serves\n that can honour it. Listing channels narrows that set and never widens it: a `transfer` listed for `chat` is\n still ignored there, because the chat channel has no call to transfer."},"annotations":{"type":"array","items":{"type":"object"}}},"required":["id"]}},"required":["delegation"]},{"type":"object","title":"transfer","properties":{"transfer":{"type":"object","description":"Transfer the call to a PBX destination. Honoured on the voice channel. One capability entry per transfer target.","properties":{"name":{"type":"string","description":"Tool name exposed to the model. Auto-generated when omitted."},"description":{"type":"string","description":"Description for the model to decide when to transfer."},"variant":{"type":"object"},"context":{"type":"string","description":"Routing context for PREDEFINED transfers."},"extension":{"type":"string","description":"Extension or phone number for PREDEFINED transfers."},"pipeline":{"type":"object"},"channels":{"type":"array","items":{"type":"object"},"uniqueItems":true,"description":"Channels this capability is offered on. Absent or empty — the default — means every channel the agent serves\n that can honour it. Listing channels narrows that set and never widens it: a `transfer` listed for `chat` is\n still ignored there, because the chat channel has no call to transfer."},"annotations":{"type":"array","items":{"type":"object"}}}}},"required":["transfer"]},{"type":"object","title":"suggestions","properties":{"suggestions":{"type":"object","description":"Emit suggested replies for human operators. Honoured on the chat channel.","properties":{"name":{"type":"string","description":"Tool name. Auto-generated when omitted."},"instructions":{"type":"string","maxLength":2000,"minLength":1,"description":"Steers what the suggestions say. Appended to the platform's own generation prompt rather than\n replacing it, so the shape of a suggestion — short, tappable, a plausible next thing to send —\n stays the platform's to keep, and this narrows the subject: 'offer a callback and a link to the\n status page, never a discount'.\n\n NOT a tool description, unlike the `description` every other capability carries: suggestions are\n not a tool the model chooses to call. They are generated from a reply that has already been\n written, so there is no decision to describe — only the generation to steer.\n\n Absent means the platform's prompt alone."},"channels":{"type":"array","items":{"type":"object"},"uniqueItems":true,"description":"Channels this capability is offered on. Absent or empty — the default — means every channel the agent serves\n that can honour it. Listing channels narrows that set and never widens it: a `transfer` listed for `chat` is\n still ignored there, because the chat channel has no call to transfer."},"annotations":{"type":"array","items":{"type":"object"}}}}},"required":["suggestions"]},{"type":"object","title":"hangup","properties":{"hangup":{"type":"object","description":"Let the model end the call. Honoured on the voice channel.","properties":{"name":{"type":"string","description":"Tool name exposed to the model. Auto-generated when omitted."},"description":{"type":"string","description":"Description for the model to decide when to hang up."},"pipeline":{"type":"object"},"channels":{"type":"array","items":{"type":"object"},"uniqueItems":true,"description":"Channels this capability is offered on. Absent or empty — the default — means every channel the agent serves\n that can honour it. Listing channels narrows that set and never widens it: a `transfer` listed for `chat` is\n still ignored there, because the chat channel has no call to transfer."},"annotations":{"type":"array","items":{"type":"object"}}}}},"required":["hangup"]},{"type":"object","title":"wait","properties":{"wait":{"type":"object","description":"Let the model hold the call while it works (e.g. a lookup), keeping the caller on the line. Honoured on the voice channel.","properties":{"name":{"type":"string","description":"Tool name exposed to the model. Auto-generated when omitted."},"description":{"type":"string","description":"Description for the model to decide when to wait."},"message":{"type":"string","description":"Message spoken when entering the wait state. Spoken in every language of the call while `messages` is absent or empty; not consulted once `messages` carries any entry."},"messages":{"type":"array","items":{"type":"object"},"description":"Per-language wait messages. Same rule as `AgentTool.startMessages`: the entry for the call's current language is spoken, a language with no entry is silent, and with no effective speech configuration `message` is spoken."},"channels":{"type":"array","items":{"type":"object"},"uniqueItems":true,"description":"Channels this capability is offered on. Absent or empty — the default — means every channel the agent serves\n that can honour it. Listing channels narrows that set and never widens it: a `transfer` listed for `chat` is\n still ignored there, because the chat channel has no call to transfer."},"annotations":{"type":"array","items":{"type":"object"}}}}},"required":["wait"]}]},"description":"Capabilities offered to the model *in addition* to `engine.ai.capabilities` while this step runs,\n configured exactly as they are there — the same union, the same structures. Use it to give a step reach\n it should not have everywhere: authoring the refund tool on the refund step, rather than on the agent, is\n also what keeps it out of every other step.\n\n Additive only, by design. A step cannot drop an agent-level capability, so it cannot strand a session by\n taking away the `handover` or `transfer` its channel needs, and the runtime keeps one rule for the turn's\n tool set. Skills the model loads (`engine.ai.skills`) and the channel's own built-ins join it either way."}},"required":["goal"]}},"required":["conversation"]},{"type":"object","title":"message","properties":{"message":{"type":"object","description":"Say one thing — spoken on voice, posted on chat / assistant — then move on. The line is either authored verbatim or generated at the step; either way the agent says it once and the transitions are evaluated, with no turn handed to the user.","properties":{"content":{"description":"Where the step's line comes from. Exactly one member is set: `text` is authored, `generate` is written by the model at the step.","oneOf":[{"type":"object","title":"text","properties":{"text":{"type":"string","maxLength":4000,"minLength":1,"description":"The line, verbatim. No model in the loop, which is what makes it the right content for a disclosure, a policy sentence or a legally worded confirmation."}},"required":["text"]},{"type":"object","title":"generate","properties":{"generate":{"type":"object","description":"A line the model writes at this step, from the conversation and whatever the graph has gathered — 'greet the\n caller by the name on the contact record and mention any open orders'. One model call, no tools offered, no\n waiting for a reply: the agent says exactly one thing and the step's transitions are evaluated.\n\n The difference from a conversation step is that this is a *speech act*, not a stretch of conversation: the\n model cannot decide to stay quiet, cannot act, and cannot keep the step. Use it where the words must fit the\n data but the shape of the session must not depend on how they came out.","properties":{"instructions":{"type":"string","maxLength":4000,"minLength":1,"description":"What to say, as an instruction to the model rather than the words themselves. Supports handlebars resolved against workflow variables, agent variables and session context, so an action's `output` captured a step earlier is available verbatim (e.g. 'Greet {{contact.firstName}} and mention their {{contact.openOrders}} open orders')."}},"required":["instructions"]}},"required":["generate"]}]},"channels":{"type":"object","description":"Per-channel settings of a message step: how the line is protected when spoken, and what is offered with it when posted.","properties":{"voice":{"type":"object","description":"Voice-channel settings for a message step, honoured while the line is spoken. Ignored on other channels.","properties":{"interruptions":{"type":"object"}}},"chat":{"type":"object","description":"Chat-channel settings for a message step, honoured where a line is posted rather than spoken. Ignored on voice.","properties":{"suggestions":{"type":"array","items":{"type":"object"},"maxItems":10,"minItems":1,"description":"Reply buttons posted with the line, e.g. 'Yes, go ahead' / 'No, not now' under a confirmation. Delivered\n as an x-bees `actions` element of `button` elements, so a tap either sends a reply — which the step's\n transitions then read like any other user message — or opens a link. Only offered on a fixed line: a\n conversation step is the model's to word, and buttons authored under words it has not chosen yet would\n contradict each other."}}}}}},"required":["content"]}},"required":["message"]},{"type":"object","title":"action","properties":{"action":{"type":"object","description":"A step that performs one capability. The runtime performs it on arrival — no model decision about *whether*\n to act, and no tool offered to the model for it. Which is the point: an action the graph reached is an action\n that happens, at the cost of nothing.","properties":{"capability":{"description":"The capability a step performs, run by the runtime rather than chosen by the model. A subset of\n AgentCapability, reusing its structures verbatim: these are the members that describe a single action with a\n beginning and an end. The rest of AgentCapability (`kb`, `webSearch`, `webFetch`, `sandbox`, `suggestions`)\n describes reach the model draws on while it talks, so it belongs on a conversation step's `capabilities`\n rather than on a step of its own.\n\n Configuration comes from the capability itself and nothing is added here: a tool step's fixed arguments are\n `AgentTool.fields` predefined handlers, its spoken lead-in is `AgentTool.startMessage`, a transfer's line\n before the hand-off is `AgentTransferPipeline.instructions`, an invoked agent's inputs are\n `AgentCallCapability.variables`. Anything left unpinned is filled by the model at the step, per the tool's\n input schema.","oneOf":[{"type":"object","title":"tool","properties":{"tool":{"type":"object","properties":{"id":{"type":"string","maxLength":12,"minLength":12,"pattern":"^[0-9A-Za-z]{12}$","description":"Unique identifier of the tool using nanoid format"},"toolVersion":{"type":"number","minimum":1,"description":"Pin a specific published tool version. When omitted, the tool's latestVersion is resolved at load, so a republished tool reaches this agent without a new agent version. Pin it when `fields` must keep aiming at an exact input schema."},"description":{"type":"string","maxLength":4096,"minLength":1,"description":"Override the tool's description for this agent. When omitted, the tool's own description is used."},"fields":{"type":"array","items":{"type":"object"},"description":"Per-field overrides on the tool input schema: pin fixed values (hidden from the model) or refine field descriptions. Fields not listed are filled by the model per the tool schema. Paths are resolved against the input schema of the tool version in effect — pin `toolVersion` when they must not move."},"startMessage":{"type":"string","description":"Message spoken when the tool call starts. Honoured on the voice channel. Spoken in every language of the call while `startMessages` is absent or empty; not consulted once `startMessages` carries any entry."},"startMessages":{"type":"array","items":{"type":"object"},"description":"Per-language start messages. When present and non-empty, the entry for the call's current\n language is spoken and `startMessage` is not consulted; a language with no entry is silent.\n When absent or empty, `startMessage` is spoken as before. On a call with no effective speech\n configuration — no configured languages, or a PBX below protocol 2 — `startMessage` is spoken\n regardless, because there is no current language to choose by."},"pipeline":{"type":"object"},"approval":{"type":"object"},"channels":{"type":"array","items":{"type":"object"},"uniqueItems":true,"description":"Channels this capability is offered on. Absent or empty — the default — means every channel the agent serves\n that can honour it. Listing channels narrows that set and never widens it: a `transfer` listed for `chat` is\n still ignored there, because the chat channel has no call to transfer."},"annotations":{"type":"array","items":{"type":"object"}}},"required":["id"]}},"required":["tool"]},{"type":"object","title":"connector","properties":{"connector":{"type":"object","description":"A configured connector (wilma-tools) exposed to the model as a single meta-tool named\n after the connector. The model calls the reserved `help` action to learn the callable\n actions, then dispatches an action through the same tool. Progressive disclosure runs\n through the connector tool itself, not the skills catalogue.","properties":{"id":{"type":"string","maxLength":12,"minLength":12,"pattern":"^[0-9A-Za-z]{12}$","description":"Configured connector instance in wilma-tools. Its pinned definition, permission `grant` and authorization govern what the model can discover via `help` and call. There is no version pin here, unlike `AgentTool.toolVersion`: the action catalogue and its schemas are already frozen by the connector's pinned definition, while `grant` and authorization are read live so permission changes apply at once."},"startMessage":{"type":"string","description":"Message spoken when a connector action call starts. Honoured on the voice channel. Mirrors `AgentTool.startMessage`. Not applied to the read-only `help` action. Not consulted once `startMessages` carries any entry."},"startMessages":{"type":"array","items":{"type":"object"},"description":"Forward-compatible per-language connector start messages, mirroring `AgentTool.startMessages`. Accepted and stored so clients can configure connector and tool phrases consistently, but currently ignored at execution time because the voice runtime does not read connector start messages yet. Runtime enablement is tracked in wilma-services docs/todo.md item 8."},"pipeline":{"type":"object"},"approval":{"type":"object"},"channels":{"type":"array","items":{"type":"object"},"uniqueItems":true,"description":"Channels this capability is offered on. Absent or empty — the default — means every channel the agent serves\n that can honour it. Listing channels narrows that set and never widens it: a `transfer` listed for `chat` is\n still ignored there, because the chat channel has no call to transfer."},"annotations":{"type":"array","items":{"type":"object"}}},"required":["id"]}},"required":["connector"]},{"type":"object","title":"agent","properties":{"agent":{"type":"object","description":"Invoke another agent as a dedicated execution with its own state. The called agent runs independently and returns its result.","properties":{"agentId":{"type":"string","maxLength":12,"minLength":12},"agentVersion":{"type":"number","minimum":1,"description":"Pin a specific published version of the called agent. When absent, the called agent's deployment configuration selects the version at invocation time."},"access":{"type":"object"},"variables":{"type":"array","items":{"type":"object"}},"channels":{"type":"array","items":{"type":"object"},"uniqueItems":true,"description":"Channels this capability is offered on. Absent or empty — the default — means every channel the agent serves\n that can honour it. Listing channels narrows that set and never widens it: a `transfer` listed for `chat` is\n still ignored there, because the chat channel has no call to transfer."}},"required":["agentId"]}},"required":["agent"]},{"type":"object","title":"handover","properties":{"handover":{"type":"object","description":"Hand the conversation over to a human and leave it. Honoured on conversation channels (chat, assistant).","properties":{"name":{"type":"string","description":"Tool name exposed to the model. Auto-generated when omitted."},"description":{"type":"string","description":"Description for the model to decide when to hand over."},"variant":{"type":"object"},"agent":{"type":"string","maxLength":12,"minLength":12,"description":"Wilma agent to hand over to when variant = AGENT."},"agentVersion":{"type":"number","minimum":1,"description":"Pin a specific published version of the target agent when variant = AGENT. When absent, the target's deployment configuration selects the version at handover time."},"service":{"type":"string","description":"x-bees service id to hand over to when variant = SERVICE."},"silent":{"type":"boolean","description":"Hand over without posting a visible message to the user."},"pipeline":{"type":"object"},"channels":{"type":"array","items":{"type":"object"},"uniqueItems":true,"description":"Channels this capability is offered on. Absent or empty — the default — means every channel the agent serves\n that can honour it. Listing channels narrows that set and never widens it: a `transfer` listed for `chat` is\n still ignored there, because the chat channel has no call to transfer."},"annotations":{"type":"array","items":{"type":"object"}}}}},"required":["handover"]},{"type":"object","title":"delegation","properties":{"delegation":{"type":"object","description":"Delegate the conversation to another agent (max 3 transitions per turn). One capability entry per delegate target.","properties":{"name":{"type":"string","description":"Tool name exposed to the model. Auto-generated when omitted."},"description":{"type":"string","description":"Description for the model to decide when to delegate."},"id":{"type":"string","maxLength":12,"minLength":12,"description":"Agent to delegate to."},"agentVersion":{"type":"number","minimum":1,"description":"Pin a specific published version of the delegate agent. When absent, the delegate's deployment configuration selects the version at delegation time."},"channels":{"type":"array","items":{"type":"object"},"uniqueItems":true,"description":"Channels this capability is offered on. Absent or empty — the default — means every channel the agent serves\n that can honour it. Listing channels narrows that set and never widens it: a `transfer` listed for `chat` is\n still ignored there, because the chat channel has no call to transfer."},"annotations":{"type":"array","items":{"type":"object"}}},"required":["id"]}},"required":["delegation"]},{"type":"object","title":"transfer","properties":{"transfer":{"type":"object","description":"Transfer the call to a PBX destination. Honoured on the voice channel. One capability entry per transfer target.","properties":{"name":{"type":"string","description":"Tool name exposed to the model. Auto-generated when omitted."},"description":{"type":"string","description":"Description for the model to decide when to transfer."},"variant":{"type":"object"},"context":{"type":"string","description":"Routing context for PREDEFINED transfers."},"extension":{"type":"string","description":"Extension or phone number for PREDEFINED transfers."},"pipeline":{"type":"object"},"channels":{"type":"array","items":{"type":"object"},"uniqueItems":true,"description":"Channels this capability is offered on. Absent or empty — the default — means every channel the agent serves\n that can honour it. Listing channels narrows that set and never widens it: a `transfer` listed for `chat` is\n still ignored there, because the chat channel has no call to transfer."},"annotations":{"type":"array","items":{"type":"object"}}}}},"required":["transfer"]},{"type":"object","title":"hangup","properties":{"hangup":{"type":"object","description":"Let the model end the call. Honoured on the voice channel.","properties":{"name":{"type":"string","description":"Tool name exposed to the model. Auto-generated when omitted."},"description":{"type":"string","description":"Description for the model to decide when to hang up."},"pipeline":{"type":"object"},"channels":{"type":"array","items":{"type":"object"},"uniqueItems":true,"description":"Channels this capability is offered on. Absent or empty — the default — means every channel the agent serves\n that can honour it. Listing channels narrows that set and never widens it: a `transfer` listed for `chat` is\n still ignored there, because the chat channel has no call to transfer."},"annotations":{"type":"array","items":{"type":"object"}}}}},"required":["hangup"]},{"type":"object","title":"wait","properties":{"wait":{"type":"object","description":"Let the model hold the call while it works (e.g. a lookup), keeping the caller on the line. Honoured on the voice channel.","properties":{"name":{"type":"string","description":"Tool name exposed to the model. Auto-generated when omitted."},"description":{"type":"string","description":"Description for the model to decide when to wait."},"message":{"type":"string","description":"Message spoken when entering the wait state. Spoken in every language of the call while `messages` is absent or empty; not consulted once `messages` carries any entry."},"messages":{"type":"array","items":{"type":"object"},"description":"Per-language wait messages. Same rule as `AgentTool.startMessages`: the entry for the call's current language is spoken, a language with no entry is silent, and with no effective speech configuration `message` is spoken."},"channels":{"type":"array","items":{"type":"object"},"uniqueItems":true,"description":"Channels this capability is offered on. Absent or empty — the default — means every channel the agent serves\n that can honour it. Listing channels narrows that set and never widens it: a `transfer` listed for `chat` is\n still ignored there, because the chat channel has no call to transfer."},"annotations":{"type":"array","items":{"type":"object"}}}}},"required":["wait"]}]},"forceToolCall":{"type":"boolean","description":"Run the step as one model call with the tool choice pinned to this capability, instead of calling it\n straight from the runtime. The model still cannot decide *whether* to call it — only what to put in the\n arguments left unpinned, which is what a lookup keyed on something nobody can template ('the symptom the\n caller just described') needs.\n\n Costs one model call where the default costs none, so it is opt-in. Honoured where the action has\n arguments a model can write — `tool`, `connector`, `agent`, and a `transfer` whose target the model\n chooses; ignored elsewhere. Leaving it false while a required argument is unpinned is an authoring error,\n not a runtime one: nothing would be there to fill it."},"output":{"type":"string","maxLength":128,"minLength":1,"pattern":"^[a-zA-Z0-9_]+$","description":"Workflow variable the action's result is written to, so later `expression` conditions can branch on it and\n later steps can template it (e.g. capture a lookup into `policy`, then branch on\n `{ \"==\": [{ \"var\": \"policy.status\" }, \"active\"] }`). Absent means the result is not captured; the\n model still sees it in the conversation per the capability's own pipeline.\n\n Written on success only. A failed action leaves the variable as it was and takes a `failure` transition\n instead — see AgentWorkflowFailureCondition — so a graph can never mistake an error for a result."}},"required":["capability"]}},"required":["action"]},{"type":"object","title":"variables","properties":{"variables":{"type":"object","description":"Write workflow variables. The step nobody asks for until the first time a graph has to remember something between two branches.","properties":{"assignments":{"type":"array","items":{"type":"object","description":"One `name = value` write. Exactly one of `value` or `expression` is set: `value` renders text, `expression` computes.","properties":{"name":{"type":"string","maxLength":128,"minLength":1,"pattern":"^[a-zA-Z0-9_]+$"},"value":{"type":"string","maxLength":4000,"description":"Text written to the variable, with handlebars resolved against workflow variables, agent variables and session context (e.g. '{{lookup.status}}'). Empty clears the variable."},"expression":{"description":"A JsonLogic expression computing the value instead of rendering it, which is what makes arithmetic possible: `{ \"+\": [{ \"var\": \"attempts\" }, 1] }` is the retry counter a bounded loop needs, written with no model in the loop."}},"required":["name"]},"maxItems":50,"minItems":1}},"required":["assignments"]}},"required":["variables"]},{"type":"object","title":"workflow","properties":{"workflow":{"type":"object","description":"Run a nested workflow and continue once it returns.\n\n The unit of decomposition: a twelve-step identity check is called from the front door, from the booking flow\n and from the complaints line, so it is authored once and referenced by a single step at each call site. The\n nested workflow shares this session and its variables; it returns through its `result` steps, which is what\n the calling step's `result` transitions branch on. A nested workflow that instead reaches an `end` or a\n terminal action does what it says — the session is over and nothing comes back.","properties":{"workflow":{"type":"string","maxLength":64,"minLength":1,"pattern":"^[a-zA-Z0-9_-]+$","description":"The nested workflow to run. Must be declared in `AgentWorkflow.nested`, and must not run itself directly or through a ring of nested workflows."},"output":{"type":"string","maxLength":128,"minLength":1,"pattern":"^[a-zA-Z0-9_]+$","description":"Workflow variable the nested workflow's result `value` is written to. Absent means the value is discarded — which is the usual case when the branch taken is the whole answer."}},"required":["workflow"]}},"required":["workflow"]},{"type":"object","title":"result","properties":{"result":{"type":"object","description":"End this workflow and hand control back to whatever invoked it.\n\n In a nested workflow the invoking step resumes and branches on which result was reached (see\n AgentWorkflowResultCondition). In the main graph of a workflow-channel agent the run finishes and the result\n is returned to the caller — the agent, automation or API request that started it. In the main graph of a\n voice / chat / assistant agent there is no invoker, so it behaves as `end`.","properties":{"value":{"type":"string","maxLength":8000,"minLength":1,"description":"What this result returns to the invoker. Supports handlebars resolved against workflow variables, agent variables and session context (e.g. '{{policy.number}}'). Absent returns the result's id alone, which is enough when the caller only branches on which result was reached."}}}},"required":["result"]},{"type":"object","title":"end","properties":{"end":{"type":"object","description":"The session is over. Terminal and configuration-free by design: a closing line is a `message` step in front of it, and releasing a call is the `hangup` action — both already exist, and neither needs restating here."}},"required":["end"]}]},"transitions":{"type":"array","items":{"type":"object","description":"One way out of a step.","properties":{"target":{"type":"string","maxLength":64,"minLength":1,"pattern":"^[a-zA-Z0-9_-]+$","description":"Step to move to. Must be a step of the same workflow — a nested workflow is entered through a `workflow` step, not by pointing a transition into it."},"condition":{"description":"How the runtime decides to take a transition. Exactly one member is set.","oneOf":[{"type":"object","title":"always","properties":{"always":{"type":"object","description":"Unconditional. Only legal as a step's single transition: two unconditional exits are a coin toss, not a routing decision."}},"required":["always"]},{"type":"object","title":"prompt","properties":{"prompt":{"type":"object","description":"A condition the model judges against the conversation, e.g. 'the caller has said they will try the fix'.\n\n Phrase it as something the **caller** has done. A condition describing what the *agent* has done — 'the\n caller has a next step to try' — is true the instant the agent names one, so the model leaves before the\n caller has answered, and one early exit cascades through the rest of the graph. Rewording the same exits from\n agent actions to caller actions, changing nothing else, moved three models from 58–64 % of turns on the right\n step to 96–100 %, and removed every stray move. It is the single largest effect measured on this contract, so\n an authoring surface that accepts an agent-action condition is the defect, not the wording.","properties":{"instructions":{"type":"string","maxLength":2000,"minLength":1}},"required":["instructions"]}},"required":["prompt"]},{"type":"object","title":"expression","properties":{"expression":{"type":"object","description":"A branch taken when workflow state satisfies the condition — no model judgement involved, which is what makes it the right condition after an `action` or `variables` step.","properties":{"conditions":{"description":"A JsonLogic condition tree evaluated against workflow state, e.g.\n `{ \"==\": [ { \"var\": \"lookup.status\" }, \"found\" ] }`. The `var` namespace is the workflow variables\n written by `variables` steps and action `output` captures, the agent's input variables\n (`engine.ai.variables`), and the session context. An empty tree always matches. This is a\n separate language from the rule-tree AutomationConditions (wilma-workflows) and CopilotConditions\n (wilma-copilot)."}},"required":["conditions"]}},"required":["expression"]},{"type":"object","title":"result","properties":{"result":{"type":"object","description":"A branch on which result a nested workflow returned. Only meaningful on a `workflow` step; the referenced step must be a `result` step of the nested workflow that step runs.","properties":{"result":{"type":"string","maxLength":64,"minLength":1,"pattern":"^[a-zA-Z0-9_-]+$","description":"Id of the `result` step reached inside the nested workflow. An id rather than a name, so renaming a result from 'failed' to 'gave up' does not unwire every step that handles it."}},"required":["result"]}},"required":["result"]},{"type":"object","title":"failure","properties":{"failure":{"type":"object","description":"A branch taken when the step's action fails. Only meaningful on an `action` step, and the only condition a\n terminal action may carry: a transfer that finds the extension busy has not left the session, so a graph that\n cannot say what happens next simply drops the caller.\n\n Failure short-circuits — when an action fails, only `failure` transitions are considered, never the\n `expression` or `prompt` branches that were written for its result. If none matches, the session fails with\n the action's error, so a step whose capability can fail wants a catch-all last in the list.","properties":{"kinds":{"type":"array","items":{"type":"string","description":"How an action failed, as far as the runtime can tell. Deliberately a short list of kinds an author can write\n a different recovery for — a busy extension is a different flow from an expired credential — rather than a\n mirror of every provider's error codes. The message and the provider's own detail stay on the invocation\n event and the trace; a graph branches on the kind.","enum":["timeout","denied","unauthorized","not_found","invalid_input","rate_limited","unavailable","error"]},"description":"Kinds this branch handles, evaluated as a set. Absent or empty catches **any** failure — the catch-all, which the list order must therefore place after the specific branches."}}}},"required":["failure"]},{"type":"object","title":"fallback","properties":{"fallback":{"type":"object","description":"The else-branch, taken when no sibling matched. At most one per step, and reached last regardless of where it sits in the list."}},"required":["fallback"]}]}},"required":["condition","target"]},"description":"The ways out of this step. Must be non-empty on every step that stays in the session, since a step the\n runtime cannot leave is a dead end.\n\n On `result` and `end` it must be empty. On the `action` members that leave the session (`handover`,\n `delegation`, `transfer`, `hangup`) it may carry `failure` transitions and nothing else: an action that\n was supposed to leave and failed — a busy extension, a service desk with nobody on it — has not left, and\n a graph with no answer for that drops the caller."},"position":{"type":"object","description":"Canvas coordinates of a step in the builder. Authoring metadata: the runtime ignores it, and a workflow that arrives without positions is laid out automatically.","properties":{"x":{"type":"number"},"y":{"type":"number"}},"required":["x","y"]}},"required":["handler","id","label"]},"maxItems":200,"minItems":1,"description":"Steps of one graph. Ids are unique within it."},"nested":{"type":"array","items":{"type":"object","description":"A named graph the main workflow — or another nested workflow — can run, referenced by AgentWorkflowInvokeHandler.","properties":{"entry":{"type":"string","maxLength":64,"minLength":1,"pattern":"^[a-zA-Z0-9_-]+$","description":"Step the graph starts at. Must be one of `steps`."},"steps":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","maxLength":64,"minLength":1,"pattern":"^[a-zA-Z0-9_-]+$","description":"Identifier of a step within its workflow, unique per workflow (the main graph and each nested workflow are separate scopes). Stable and machine-facing: transitions reference it, and a result step's id is the name a caller branches on, so renaming a step's `label` never unwires the graph."},"label":{"type":"string","maxLength":128,"minLength":1,"description":"Human readable name of a step, e.g. 'Transfer to the sales desk'. Shown in the builder and in traces, and\n read by the model too: when this step is an exit of the live step, the label is the preview offered alongside\n the exit's condition. Never a routing key — see AgentWorkflowStepId — so renaming one never unwires a graph."},"stage":{"type":"string","maxLength":64,"minLength":1,"description":"The phase this step belongs to. Inherited by steps downstream, so a handful of tags organise a long graph."},"handler":{"description":"What a step does. Exactly one member is set. `action` carries a capability; the others are workflow-only handlers with no capability equivalent.","oneOf":[{"type":"object","title":"conversation","properties":{"conversation":{"type":"object","description":"A stretch of conversation with a goal: the model talks, may use the capabilities in scope, and the step's\n transitions are evaluated against what happens. This is the only handler that leaves control where it is\n until a transition matches — a step whose conditions all stay false simply keeps talking.\n\n Deliberately not configurable per step: the model, and how readily the step is left. The model is the\n agent's (`engine.ai.model`) because a session that swaps models mid-conversation swaps voice, latency and\n tool-calling behaviour with it. How readily a step is left is decided by how its transitions are worded —\n conditions describing what the *caller* has done keep a model on the step, and no eagerness dial moves that\n number.","properties":{"goal":{"type":"string","maxLength":10000,"minLength":1,"description":"What the agent is trying to achieve here, appended to the agent's `engine.ai.instructions`. Never a replacement for them: identity, tone and standing rules hold across every step, and a step that needs different ones is a different agent."},"entry":{"type":"string","description":"How a conversation step starts.","enum":["auto","speak_first","listen_first"]},"channels":{"type":"object","description":"Per-channel settings of a conversation step. Only voice has anything to say about a stretch of conversation today; the chat and assistant surfaces deliver it as they deliver every other reply.","properties":{"voice":{"type":"object"}}},"capabilities":{"type":"array","items":{"oneOf":[{"type":"object","title":"tool","properties":{"tool":{"type":"object"}},"required":["tool"]},{"type":"object","title":"connector","properties":{"connector":{"type":"object"}},"required":["connector"]},{"type":"object","title":"kb","properties":{"kb":{"type":"object"}},"required":["kb"]},{"type":"object","title":"sandbox","properties":{"sandbox":{"type":"object"}},"required":["sandbox"]},{"type":"object","title":"webSearch","properties":{"webSearch":{"type":"object"}},"required":["webSearch"]},{"type":"object","title":"webFetch","properties":{"webFetch":{"type":"object"}},"required":["webFetch"]},{"type":"object","title":"agent","properties":{"agent":{"type":"object"}},"required":["agent"]},{"type":"object","title":"handover","properties":{"handover":{"type":"object"}},"required":["handover"]},{"type":"object","title":"delegation","properties":{"delegation":{"type":"object"}},"required":["delegation"]},{"type":"object","title":"transfer","properties":{"transfer":{"type":"object"}},"required":["transfer"]},{"type":"object","title":"suggestions","properties":{"suggestions":{"type":"object"}},"required":["suggestions"]},{"type":"object","title":"hangup","properties":{"hangup":{"type":"object"}},"required":["hangup"]},{"type":"object","title":"wait","properties":{"wait":{"type":"object"}},"required":["wait"]}]},"description":"Capabilities offered to the model *in addition* to `engine.ai.capabilities` while this step runs,\n configured exactly as they are there — the same union, the same structures. Use it to give a step reach\n it should not have everywhere: authoring the refund tool on the refund step, rather than on the agent, is\n also what keeps it out of every other step.\n\n Additive only, by design. A step cannot drop an agent-level capability, so it cannot strand a session by\n taking away the `handover` or `transfer` its channel needs, and the runtime keeps one rule for the turn's\n tool set. Skills the model loads (`engine.ai.skills`) and the channel's own built-ins join it either way."}},"required":["goal"]}},"required":["conversation"]},{"type":"object","title":"message","properties":{"message":{"type":"object","description":"Say one thing — spoken on voice, posted on chat / assistant — then move on. The line is either authored verbatim or generated at the step; either way the agent says it once and the transitions are evaluated, with no turn handed to the user.","properties":{"content":{"description":"Where the step's line comes from. Exactly one member is set: `text` is authored, `generate` is written by the model at the step.","oneOf":[{"type":"object","title":"text","properties":{"text":{"type":"string","maxLength":4000,"minLength":1,"description":"The line, verbatim. No model in the loop, which is what makes it the right content for a disclosure, a policy sentence or a legally worded confirmation."}},"required":["text"]},{"type":"object","title":"generate","properties":{"generate":{"type":"object"}},"required":["generate"]}]},"channels":{"type":"object","description":"Per-channel settings of a message step: how the line is protected when spoken, and what is offered with it when posted.","properties":{"voice":{"type":"object"},"chat":{"type":"object"}}}},"required":["content"]}},"required":["message"]},{"type":"object","title":"action","properties":{"action":{"type":"object","description":"A step that performs one capability. The runtime performs it on arrival — no model decision about *whether*\n to act, and no tool offered to the model for it. Which is the point: an action the graph reached is an action\n that happens, at the cost of nothing.","properties":{"capability":{"description":"The capability a step performs, run by the runtime rather than chosen by the model. A subset of\n AgentCapability, reusing its structures verbatim: these are the members that describe a single action with a\n beginning and an end. The rest of AgentCapability (`kb`, `webSearch`, `webFetch`, `sandbox`, `suggestions`)\n describes reach the model draws on while it talks, so it belongs on a conversation step's `capabilities`\n rather than on a step of its own.\n\n Configuration comes from the capability itself and nothing is added here: a tool step's fixed arguments are\n `AgentTool.fields` predefined handlers, its spoken lead-in is `AgentTool.startMessage`, a transfer's line\n before the hand-off is `AgentTransferPipeline.instructions`, an invoked agent's inputs are\n `AgentCallCapability.variables`. Anything left unpinned is filled by the model at the step, per the tool's\n input schema.","oneOf":[{"type":"object","title":"tool","properties":{"tool":{"type":"object"}},"required":["tool"]},{"type":"object","title":"connector","properties":{"connector":{"type":"object"}},"required":["connector"]},{"type":"object","title":"agent","properties":{"agent":{"type":"object"}},"required":["agent"]},{"type":"object","title":"handover","properties":{"handover":{"type":"object"}},"required":["handover"]},{"type":"object","title":"delegation","properties":{"delegation":{"type":"object"}},"required":["delegation"]},{"type":"object","title":"transfer","properties":{"transfer":{"type":"object"}},"required":["transfer"]},{"type":"object","title":"hangup","properties":{"hangup":{"type":"object"}},"required":["hangup"]},{"type":"object","title":"wait","properties":{"wait":{"type":"object"}},"required":["wait"]}]},"forceToolCall":{"type":"boolean","description":"Run the step as one model call with the tool choice pinned to this capability, instead of calling it\n straight from the runtime. The model still cannot decide *whether* to call it — only what to put in the\n arguments left unpinned, which is what a lookup keyed on something nobody can template ('the symptom the\n caller just described') needs.\n\n Costs one model call where the default costs none, so it is opt-in. Honoured where the action has\n arguments a model can write — `tool`, `connector`, `agent`, and a `transfer` whose target the model\n chooses; ignored elsewhere. Leaving it false while a required argument is unpinned is an authoring error,\n not a runtime one: nothing would be there to fill it."},"output":{"type":"string","maxLength":128,"minLength":1,"pattern":"^[a-zA-Z0-9_]+$","description":"Workflow variable the action's result is written to, so later `expression` conditions can branch on it and\n later steps can template it (e.g. capture a lookup into `policy`, then branch on\n `{ \"==\": [{ \"var\": \"policy.status\" }, \"active\"] }`). Absent means the result is not captured; the\n model still sees it in the conversation per the capability's own pipeline.\n\n Written on success only. A failed action leaves the variable as it was and takes a `failure` transition\n instead — see AgentWorkflowFailureCondition — so a graph can never mistake an error for a result."}},"required":["capability"]}},"required":["action"]},{"type":"object","title":"variables","properties":{"variables":{"type":"object","description":"Write workflow variables. The step nobody asks for until the first time a graph has to remember something between two branches.","properties":{"assignments":{"type":"array","items":{"type":"object","description":"One `name = value` write. Exactly one of `value` or `expression` is set: `value` renders text, `expression` computes.","properties":{"name":{"type":"string","maxLength":128,"minLength":1,"pattern":"^[a-zA-Z0-9_]+$"},"value":{"type":"string","maxLength":4000,"description":"Text written to the variable, with handlebars resolved against workflow variables, agent variables and session context (e.g. '{{lookup.status}}'). Empty clears the variable."},"expression":{"description":"A JsonLogic expression computing the value instead of rendering it, which is what makes arithmetic possible: `{ \"+\": [{ \"var\": \"attempts\" }, 1] }` is the retry counter a bounded loop needs, written with no model in the loop."}},"required":["name"]},"maxItems":50,"minItems":1}},"required":["assignments"]}},"required":["variables"]},{"type":"object","title":"workflow","properties":{"workflow":{"type":"object","description":"Run a nested workflow and continue once it returns.\n\n The unit of decomposition: a twelve-step identity check is called from the front door, from the booking flow\n and from the complaints line, so it is authored once and referenced by a single step at each call site. The\n nested workflow shares this session and its variables; it returns through its `result` steps, which is what\n the calling step's `result` transitions branch on. A nested workflow that instead reaches an `end` or a\n terminal action does what it says — the session is over and nothing comes back.","properties":{"workflow":{"type":"string","maxLength":64,"minLength":1,"pattern":"^[a-zA-Z0-9_-]+$","description":"The nested workflow to run. Must be declared in `AgentWorkflow.nested`, and must not run itself directly or through a ring of nested workflows."},"output":{"type":"string","maxLength":128,"minLength":1,"pattern":"^[a-zA-Z0-9_]+$","description":"Workflow variable the nested workflow's result `value` is written to. Absent means the value is discarded — which is the usual case when the branch taken is the whole answer."}},"required":["workflow"]}},"required":["workflow"]},{"type":"object","title":"result","properties":{"result":{"type":"object","description":"End this workflow and hand control back to whatever invoked it.\n\n In a nested workflow the invoking step resumes and branches on which result was reached (see\n AgentWorkflowResultCondition). In the main graph of a workflow-channel agent the run finishes and the result\n is returned to the caller — the agent, automation or API request that started it. In the main graph of a\n voice / chat / assistant agent there is no invoker, so it behaves as `end`.","properties":{"value":{"type":"string","maxLength":8000,"minLength":1,"description":"What this result returns to the invoker. Supports handlebars resolved against workflow variables, agent variables and session context (e.g. '{{policy.number}}'). Absent returns the result's id alone, which is enough when the caller only branches on which result was reached."}}}},"required":["result"]},{"type":"object","title":"end","properties":{"end":{"type":"object","description":"The session is over. Terminal and configuration-free by design: a closing line is a `message` step in front of it, and releasing a call is the `hangup` action — both already exist, and neither needs restating here."}},"required":["end"]}]},"transitions":{"type":"array","items":{"type":"object","description":"One way out of a step.","properties":{"target":{"type":"string","maxLength":64,"minLength":1,"pattern":"^[a-zA-Z0-9_-]+$","description":"Step to move to. Must be a step of the same workflow — a nested workflow is entered through a `workflow` step, not by pointing a transition into it."},"condition":{"description":"How the runtime decides to take a transition. Exactly one member is set.","oneOf":[{"type":"object","title":"always","properties":{"always":{"type":"object","description":"Unconditional. Only legal as a step's single transition: two unconditional exits are a coin toss, not a routing decision."}},"required":["always"]},{"type":"object","title":"prompt","properties":{"prompt":{"type":"object","description":"A condition the model judges against the conversation, e.g. 'the caller has said they will try the fix'.\n\n Phrase it as something the **caller** has done. A condition describing what the *agent* has done — 'the\n caller has a next step to try' — is true the instant the agent names one, so the model leaves before the\n caller has answered, and one early exit cascades through the rest of the graph. Rewording the same exits from\n agent actions to caller actions, changing nothing else, moved three models from 58–64 % of turns on the right\n step to 96–100 %, and removed every stray move. It is the single largest effect measured on this contract, so\n an authoring surface that accepts an agent-action condition is the defect, not the wording.","properties":{"instructions":{"type":"string","maxLength":2000,"minLength":1}},"required":["instructions"]}},"required":["prompt"]},{"type":"object","title":"expression","properties":{"expression":{"type":"object","description":"A branch taken when workflow state satisfies the condition — no model judgement involved, which is what makes it the right condition after an `action` or `variables` step.","properties":{"conditions":{"description":"A JsonLogic condition tree evaluated against workflow state, e.g.\n `{ \"==\": [ { \"var\": \"lookup.status\" }, \"found\" ] }`. The `var` namespace is the workflow variables\n written by `variables` steps and action `output` captures, the agent's input variables\n (`engine.ai.variables`), and the session context. An empty tree always matches. This is a\n separate language from the rule-tree AutomationConditions (wilma-workflows) and CopilotConditions\n (wilma-copilot)."}},"required":["conditions"]}},"required":["expression"]},{"type":"object","title":"result","properties":{"result":{"type":"object","description":"A branch on which result a nested workflow returned. Only meaningful on a `workflow` step; the referenced step must be a `result` step of the nested workflow that step runs.","properties":{"result":{"type":"string","maxLength":64,"minLength":1,"pattern":"^[a-zA-Z0-9_-]+$","description":"Id of the `result` step reached inside the nested workflow. An id rather than a name, so renaming a result from 'failed' to 'gave up' does not unwire every step that handles it."}},"required":["result"]}},"required":["result"]},{"type":"object","title":"failure","properties":{"failure":{"type":"object","description":"A branch taken when the step's action fails. Only meaningful on an `action` step, and the only condition a\n terminal action may carry: a transfer that finds the extension busy has not left the session, so a graph that\n cannot say what happens next simply drops the caller.\n\n Failure short-circuits — when an action fails, only `failure` transitions are considered, never the\n `expression` or `prompt` branches that were written for its result. If none matches, the session fails with\n the action's error, so a step whose capability can fail wants a catch-all last in the list.","properties":{"kinds":{"type":"array","items":{"type":"object"},"description":"Kinds this branch handles, evaluated as a set. Absent or empty catches **any** failure — the catch-all, which the list order must therefore place after the specific branches."}}}},"required":["failure"]},{"type":"object","title":"fallback","properties":{"fallback":{"type":"object","description":"The else-branch, taken when no sibling matched. At most one per step, and reached last regardless of where it sits in the list."}},"required":["fallback"]}]}},"required":["condition","target"]},"description":"The ways out of this step. Must be non-empty on every step that stays in the session, since a step the\n runtime cannot leave is a dead end.\n\n On `result` and `end` it must be empty. On the `action` members that leave the session (`handover`,\n `delegation`, `transfer`, `hangup`) it may carry `failure` transitions and nothing else: an action that\n was supposed to leave and failed — a busy extension, a service desk with nobody on it — has not left, and\n a graph with no answer for that drops the caller."},"position":{"type":"object","description":"Canvas coordinates of a step in the builder. Authoring metadata: the runtime ignores it, and a workflow that arrives without positions is laid out automatically.","properties":{"x":{"type":"number"},"y":{"type":"number"}},"required":["x","y"]}},"required":["handler","id","label"]},"maxItems":200,"minItems":1,"description":"Steps of one graph. Ids are unique within it."},"id":{"type":"string","maxLength":64,"minLength":1,"pattern":"^[a-zA-Z0-9_-]+$","description":"Identifier of a nested workflow within the agent's workflow."},"name":{"type":"string","maxLength":128,"minLength":1},"description":{"type":"string","maxLength":1000,"minLength":1,"description":"One line saying what the nested workflow is for, shown on the step that runs it."}},"required":["entry","id","name","steps"]},"description":"Graphs the main workflow can run, referenced by `workflow` steps."},"settings":{"type":"object","properties":{"maxTransitions":{"type":"number","maximum":500,"minimum":1,"description":"Give up after this many step transitions, so a cycle whose conditions all keep matching cannot run\n forever. Absent means no limit, which is the default: a deliberate loop — 'keep asking until you have the\n postcode' — is a normal shape, and silently capping it would be worse than making the author say so."},"variables":{"type":"array","items":{"type":"object","description":"One `name = value` write. Exactly one of `value` or `expression` is set: `value` renders text, `expression` computes.","properties":{"name":{"type":"string","maxLength":128,"minLength":1,"pattern":"^[a-zA-Z0-9_]+$"},"value":{"type":"string","maxLength":4000,"description":"Text written to the variable, with handlebars resolved against workflow variables, agent variables and session context (e.g. '{{lookup.status}}'). Empty clears the variable."},"expression":{"description":"A JsonLogic expression computing the value instead of rendering it, which is what makes arithmetic possible: `{ \"+\": [{ \"var\": \"attempts\" }, 1] }` is the retry counter a bounded loop needs, written with no model in the loop."}},"required":["name"]},"description":"Workflow variables seeded before the first step runs. Agent input variables (`engine.ai.variables`) are already in scope and need no entry here."},"layout":{"type":"string","description":"How the builder arranges the graph. Authoring metadata: the runtime ignores it. `stages` bands the graph by `stage`, which is what a forty-step workflow needs.","enum":["flow","compact","stages"]}}}},"required":["entry","steps"]},"guardrails":{"type":"object","description":"Turn screening, by side. Only `input` exists: it guards what reaches the model, and output screening is a separate policy with different actions, added here when it is built.","properties":{"input":{"type":"object","description":"Screening applied to what reaches the model.\n\n There is deliberately no bundle or preset-group member. A named group that expands to a set of checks\n reads like convenience and behaves like version skew: the same agent definition would mean different\n things before and after a platform release, invisibly to the tenant who published it. Listing the checks\n costs a few lines in a document the console writes anyway, and the published version then states exactly\n what it enforces.\n\n There is also no set-level default action. One field standing behind every check reads as a convenience\n and hides the thing a reader most needs to see: what a given check will actually do. A check's action is\n its own or its preset's, and both are visible on the check.","properties":{"checks":{"type":"array","items":{"type":"object","description":"One thing the checker is asked about a turn. There is deliberately no `enabled` flag: a check that\n should not run is removed, so a published version never carries a switched-off check that reads like\n policy.","properties":{"name":{"type":"string","maxLength":64,"minLength":1,"description":"Name of a check, unique within a guardrail set. Stable and machine-facing: session events and trace spans report a verdict by this name, so a reader needs no copy of the agent definition to tell which check fired."},"description":{"type":"string"},"kind":{"description":"What a check is: one of the platform's presets, or a prompt the tenant wrote. Exactly one member is set.","oneOf":[{"type":"object","title":"preset","properties":{"preset":{"type":"object","description":"A check backed by one of the platform's prebuilt prompts.","properties":{"preset":{"type":"string","description":"A prebuilt check. Each preset is a platform-maintained prompt shipped in code and versioned with the\n platform, not a tenant row — so a support question about what a preset caught has one answer. A preset\n check is otherwise an ordinary AgentGuardrailCheck: its sensitivity and action are the tenant's to set,\n and `notes` narrows its prompt without replacing it.","enum":["jailbreak","offTopic","abuse","harmful","selfHarm","pii","secrets","regulatedAdvice"]},"notes":{"type":"string","maxLength":2000,"minLength":1,"description":"Appended to the preset's prompt. For narrowing, not redefining — 'ignore card numbers read back after our own IVR prompts for them' — so the preset keeps its maintenance while fitting a tenant whose product would otherwise fight it."}},"required":["preset"]}},"required":["preset"]},{"type":"object","title":"custom","properties":{"custom":{"type":"object","description":"A check written by the tenant. A name and a prompt is the whole surface: no category enum can express the rules customers actually buy, such as refusing to quote a price before qualifying.","properties":{"instructions":{"type":"string","maxLength":10000,"minLength":1,"description":"Natural-language description of what a custom check looks for. Describe the *content* to be caught, not an instruction to the checker: 'The caller is asking about cryptocurrency investments' works, 'Block all content about crypto' does not, and a negative definition ('anything except billing questions') reliably misfires."}},"required":["instructions"]}},"required":["custom"]}]},"sensitivity":{"type":"string","description":"How much confidence a verdict needs before it counts as tripped. The checker reports a confidence per\n check and this is the threshold against it, so raising sensitivity catches more and costs more false\n positives without changing the prompt or the model: `low` trips only on high confidence, `medium` on\n high and medium, `high` on anything but a clear pass. Defaults to MEDIUM.","enum":["low","medium","high"]},"action":{"description":"What happens when a check trips. Exactly one member is set.\n\n Ordered by severity: observe < redact < block < end. When more than one check trips in the same call the\n most severe action wins and ties break on the order of `checks`, so a set can mix an observed check with\n an enforced one without the observation weakening the enforcement.\n\n There is no hand-over member yet. Handing a session to a human is a different mechanism on every channel\n — a voice transfer, a chat assignment, an assistant with no human behind it — and wiring a guardrail into\n all of them is out of proportion to the rest of this feature. Until AgentHandoverCapability has a runtime\n on more than one channel, the crisis path is a block whose message routes the caller.","oneOf":[{"type":"object","title":"observe","properties":{"observe":{"type":"object","description":"Record the verdict and let the turn through. The rollout mode: every guardrail should run here first, against real traffic, until its trips are read and its sensitivity is tuned. A tripped check still emits GuardrailTrippedEvent and still appears in the trace, which is what makes this a tuning tool rather than a switched-off check."}},"required":["observe"]},{"type":"object","title":"redact","properties":{"redact":{"type":"object","description":"Mask the matched spans and let the turn continue. The mask is chosen by the platform and is not configurable — a tenant-authored placeholder that collides with real text, or that the model reads as an instruction, is worse than having no choice. Only valid on checks that return spans (PII, SECRETS)."}},"required":["redact"]},{"type":"object","title":"block","properties":{"block":{"type":"object","description":"Refuse the turn. The agent's model is never called, no tool runs, and the caller is answered with `message` instead. The session stays alive and the conversation continues, so on a check meant to redirect rather than stop — 'let me take a few details first' — the message is doing the work.","properties":{"message":{"type":"string","maxLength":1000,"minLength":1,"description":"Said to the caller in place of the agent's reply. Falls back to a channel default when unset."}}}},"required":["block"]},{"type":"object","title":"end","properties":{"end":{"type":"object","description":"End the session. It lands in SessionStatus TERMINATED — not STOPPED, which is what a user asking to stop means — so a refused session is never read back as a clean finish. On voice this hangs up the call; on the other channels the session simply accepts no further turns.","properties":{"message":{"type":"string","maxLength":1000,"minLength":1,"description":"Said to the caller before the session ends."}}}},"required":["end"]}]}},"required":["kind","name"]},"maxItems":30,"description":"The checks to run, evaluated together in one call. Absent or empty means no screening."},"model":{"type":"string","pattern":"^(default|([a-z0-9][a-z0-9-]*://[a-zA-Z0-9._/-]+(\\?[a-zA-Z0-9._&=%-]*)?))$","description":"The model that runs the checks. Defaults to the platform's guardrail model — deliberately small and fast, and deliberately not the agent's own, whose cost and latency are what this exists to avoid spending on a bad turn."},"action":{"description":"What happens when a check trips. Exactly one member is set.\n\n Ordered by severity: observe < redact < block < end. When more than one check trips in the same call the\n most severe action wins and ties break on the order of `checks`, so a set can mix an observed check with\n an enforced one without the observation weakening the enforcement.\n\n There is no hand-over member yet. Handing a session to a human is a different mechanism on every channel\n — a voice transfer, a chat assignment, an assistant with no human behind it — and wiring a guardrail into\n all of them is out of proportion to the rest of this feature. Until AgentHandoverCapability has a runtime\n on more than one channel, the crisis path is a block whose message routes the caller.","oneOf":[{"type":"object","title":"observe","properties":{"observe":{"type":"object","description":"Record the verdict and let the turn through. The rollout mode: every guardrail should run here first, against real traffic, until its trips are read and its sensitivity is tuned. A tripped check still emits GuardrailTrippedEvent and still appears in the trace, which is what makes this a tuning tool rather than a switched-off check."}},"required":["observe"]},{"type":"object","title":"redact","properties":{"redact":{"type":"object","description":"Mask the matched spans and let the turn continue. The mask is chosen by the platform and is not configurable — a tenant-authored placeholder that collides with real text, or that the model reads as an instruction, is worse than having no choice. Only valid on checks that return spans (PII, SECRETS)."}},"required":["redact"]},{"type":"object","title":"block","properties":{"block":{"type":"object","description":"Refuse the turn. The agent's model is never called, no tool runs, and the caller is answered with `message` instead. The session stays alive and the conversation continues, so on a check meant to redirect rather than stop — 'let me take a few details first' — the message is doing the work.","properties":{"message":{"type":"string","maxLength":1000,"minLength":1,"description":"Said to the caller in place of the agent's reply. Falls back to a channel default when unset."}}}},"required":["block"]},{"type":"object","title":"end","properties":{"end":{"type":"object","description":"End the session. It lands in SessionStatus TERMINATED — not STOPPED, which is what a user asking to stop means — so a refused session is never read back as a clean finish. On voice this hangs up the call; on the other channels the session simply accepts no further turns.","properties":{"message":{"type":"string","maxLength":1000,"minLength":1,"description":"Said to the caller before the session ends."}}}},"required":["end"]}]},"mode":{"type":"string","description":"Runs the checker before the agent's model (BLOCKING) or alongside it (PARALLEL). Blocking costs the checker's latency on every turn but guarantees that a refused turn spent no tokens and fired no tool; parallel adds no latency and is the right trade on voice, where the delay is audible, at the risk that part of a reply reaches the caller and that a tool has already run before the cancellation lands. Defaults to BLOCKING.","enum":["blocking","parallel"]},"errorMode":{"type":"string","description":"What happens when the checker itself errors or times out. OPEN lets the turn through unscreened and records GuardrailFailedEvent, so an unscreened turn is a fact in the log rather than an absence; CLOSED refuses it. Defaults to OPEN: an agent that stops answering because a small model had a bad minute is a worse and far likelier outage than the rare turn that goes unscreened. Background tasks, where nobody is waiting and a retry is free, are the case for CLOSED.","enum":["open","closed"]},"window":{"type":"string","description":"How much of the conversation the checker sees. Defaults to LAST_WITH_PRIOR, because the jailbreaks worth catching are built across several turns — establish a fiction, then cash it in — and a checker shown only the last turn sees an innocuous sentence. There is no full-transcript option: screening a whole call on every turn would make the guardrail dearer than the agent.","enum":["lastMessage","lastWithPrior"]}}}}},"model":{"type":"string","pattern":"^(default|([a-z0-9][a-z0-9-]*://[a-zA-Z0-9._/-]+(\\?[a-zA-Z0-9._&=%-]*)?))$","description":"Defaults to the platform default model when omitted."},"timeZone":{"type":"string","maxLength":64,"minLength":1,"description":"IANA timezone the agent reasons about time in: it anchors the current date and time given to the model and how times are read from and written to tool arguments and replies. Shared by every channel the agent serves. Defaults to 'UTC'."},"artifacts":{"type":"boolean","description":"Whether the agent can produce artifacts (wilma-artifacts): files it writes in its sandbox and shares back during a session — a document, a PDF, a spreadsheet, a chart, a multi-page web site, a folder of files. Needs the sandbox capability. An artifact starts private to the user the session runs for, who can share it company-wide from the viewer; on the chat channel the runtime shares each one company-wide as it is created, so the link posted into the conversation opens for every signed-in colleague. Artifacts are company-internal: recipients on external transports (sms / whatsapp) or outside the company cannot open the links, so keep this disabled for customer-facing agents. Defaults to false."},"skills":{"type":"array","items":{"type":"object","description":"Reference to a skill attached to an agent.","properties":{"id":{"type":"string","maxLength":12,"minLength":12},"skillVersion":{"type":"number","minimum":1,"description":"Pin a specific published skill version. When omitted, latestVersion is resolved."},"access":{"type":"object","description":"Who is allowed to use the agent. There is no view/edit dimension — access solely grants the ability to use the agent.\n\n Where it is applied, per channel:\n - `assistant` — enforced. GetAgent on wilma-assistant refuses an agent the caller is not granted.\n - `voice` — deliberately not applied. Inbound calls are routed by the PBX dialplan, and the data ACL is the\n agent-level `settings.pbxGroupId`, not per-user grants.\n - `chat` — enforced for COLLEAGUES ONLY. wilma-chat checks the sender against this block when the sender\n belongs to the agent's own company. A sender from outside it — a Kite website visitor, an SMS or WhatsApp\n contact — is not governed by this field at all and reaches the agent under every visibility; what exposes\n an agent to them is an administrator wiring it to that surface. A refused colleague receives one short\n refusal message and no turn is started.\n A `groupId` grant is weaker here than on the assistant: this channel reads the sender's group from their\n x-bees user record, which does not always carry one. When EVERY grant on the agent is a `groupId` grant and\n the sender's group is unknown, the sender is PASSED rather than refused — refusing would lock out everyone\n whose record lacks a group, which is a large minority. Adding a `userId` or `department` grant removes that\n waiver and the agent is enforced normally, so use one of those where the restriction must hold on chat.\n (x-bees chat bots are a separate stack with their own company-level `BotAccess`; it is not this field.)\n\n When an agent is invoked as a background task its agent-level access is not used — invocation access is configured\n per reference on the calling agent (see AgentCallCapability.access).","properties":{"visibility":{"type":"string","enum":["explicit","organization","public"]},"grants":{"type":"array","items":{"description":"A user, group or department that access is granted to.","oneOf":[{"type":"object","title":"userId","properties":{"userId":{"type":"string","maxLength":12,"minLength":12}},"required":["userId"]},{"type":"object","title":"groupId","properties":{"groupId":{"type":"string"}},"required":["groupId"]},{"type":"object","title":"department","properties":{"department":{"type":"string","description":"PBX department NAME, as carried on the colleague record (`PbxColleague.department`), not a department id.\n The x-bees directory knows a colleague's department only by name, so matching by name is what lets the check\n read a field the runtime already has instead of resolving the PBX department tree on every request.\n Two consequences: renaming a department in the PBX invalidates every grant naming it, and two departments\n with the same name in different branches of the tree are one value here."}},"required":["department"]}]},"description":"Users, groups and departments granted the ability to use the agent. Applies when visibility is `explicit`."}}}},"required":["id"]},"description":"Skills attached to this agent (progressive disclosure). A skill may carry its own tools, which join this agent's toolset while that skill is active."},"capabilities":{"type":"array","items":{"oneOf":[{"type":"object","title":"tool","properties":{"tool":{"type":"object","properties":{"id":{"type":"string","maxLength":12,"minLength":12,"pattern":"^[0-9A-Za-z]{12}$","description":"Unique identifier of the tool using nanoid format"},"toolVersion":{"type":"number","minimum":1,"description":"Pin a specific published tool version. When omitted, the tool's latestVersion is resolved at load, so a republished tool reaches this agent without a new agent version. Pin it when `fields` must keep aiming at an exact input schema."},"description":{"type":"string","maxLength":4096,"minLength":1,"description":"Override the tool's description for this agent. When omitted, the tool's own description is used."},"fields":{"type":"array","items":{"type":"object","description":"Per-field override applied to a shared tool's input schema for this agent.","properties":{"path":{"type":"string","maxLength":512,"minLength":1,"pattern":"^/.*$","description":"JSON Pointer to the target property in the tool input schema."},"handler":{"oneOf":[{"type":"object","title":"guided","properties":{"guided":{"type":"object","description":"Keeps the field model-fillable but overrides its description in the schema shown to the model.","properties":{"description":{"type":"string","maxLength":1024,"minLength":1}},"required":["description"]}},"required":["guided"]},{"type":"object","title":"predefined","properties":{"predefined":{"type":"object","description":"A fixed value injected at the field path and excluded from the schema shown to the model, so the model neither sees nor sets it. A JSON value, or a string with handlebars templates resolved against agent variables and session context (e.g. '{{callerName}}').","properties":{"value":{}},"required":["value"]}},"required":["predefined"]}]}},"required":["handler","path"]},"description":"Per-field overrides on the tool input schema: pin fixed values (hidden from the model) or refine field descriptions. Fields not listed are filled by the model per the tool schema. Paths are resolved against the input schema of the tool version in effect — pin `toolVersion` when they must not move."},"startMessage":{"type":"string","description":"Message spoken when the tool call starts. Honoured on the voice channel. Spoken in every language of the call while `startMessages` is absent or empty; not consulted once `startMessages` carries any entry."},"startMessages":{"type":"array","items":{"type":"object","description":"One phrase in one language, for a capability that speaks a fixed line on the voice channel.\n Which entry is spoken is decided by the call's current language — the one the call resolved\n to at connect, or the one automatic detection switched to — matched on `language`\n CASE-INSENSITIVELY, the same rule `VoiceBotSpokenPhrase` states for the legacy stack, so a\n code written in either case reaches the same entry on either. Blank `text` means the agent\n says nothing in that language; it does NOT fall back to the language-agnostic member beside\n the list, which would speak one language's text in another language's voice.","properties":{"language":{"type":"string","maxLength":8,"minLength":2,"pattern":"^[a-z]{2,3}(-[A-Z]{2})?$","description":"BCP-47 language tag, optionally with a region subtag: 'en', 'en-US', 'pt-BR'. Deliberately a\n constrained string rather than an enum: the offerable set is a property of the selected\n recogniser and changes with it, so an enumeration here would cap the set at whatever was known\n when the model was released."},"text":{"type":"string"}},"required":["language","text"]},"description":"Per-language start messages. When present and non-empty, the entry for the call's current\n language is spoken and `startMessage` is not consulted; a language with no entry is silent.\n When absent or empty, `startMessage` is spoken as before. On a call with no effective speech\n configuration — no configured languages, or a PBX below protocol 2 — `startMessage` is spoken\n regardless, because there is no current language to choose by."},"pipeline":{"type":"object","properties":{"type":{"type":"string","description":"How the model waits for a tool and whether it synthesizes a reply from the result.","enum":["blocking_request","blocking_request_guided","async_request","async_request_guided"]},"instructions":{"type":"string","description":"Reply instructions for the *_GUIDED pipeline types. Use {{response}} to reference the tool result."}}},"approval":{"type":"object","description":"Human-in-the-loop confirmation for a tool. Presence requires approval: the agent pauses before running the tool and the session user (whoever controls the session) approves or denies inline.","properties":{"prompt":{"type":"string","description":"Optional message shown in the confirmation prompt. Supports handlebars against agent variables and the pending tool arguments. When omitted, a default prompt is shown."}}},"channels":{"type":"array","items":{"type":"string","description":"A channel an agent can serve (see AgentChannels for enablement). Also the runtime channel recorded by the channel runtimes / wilma-traces. A background task has no delivery surface and therefore no channel.","enum":["voice","chat","assistant"]},"uniqueItems":true,"description":"Channels this capability is offered on. Absent or empty — the default — means every channel the agent serves\n that can honour it. Listing channels narrows that set and never widens it: a `transfer` listed for `chat` is\n still ignored there, because the chat channel has no call to transfer."},"annotations":{"type":"array","items":{"description":"Configuration for annotations emitted after a tool completes.","oneOf":[{"type":"object","title":"tag","properties":{"tag":{"type":"object","description":"Tag annotation emitted after a tool completes.","properties":{"value":{"type":"string","description":"Static handlebars expression resolved against agent variables and helpers."},"applyOnSuccess":{"type":"boolean","description":"Apply when the tool succeeds."},"applyOnFailure":{"type":"boolean","description":"Apply when the tool fails."}}}},"required":["tag"]},{"type":"object","title":"comment","properties":{"comment":{"type":"object","description":"Comment annotation emitted after a tool completes.","properties":{"value":{"type":"string","description":"Static handlebars expression resolved against agent variables and helpers."},"applyOnSuccess":{"type":"boolean","description":"Apply when the tool succeeds."},"applyOnFailure":{"type":"boolean","description":"Apply when the tool fails."}}}},"required":["comment"]},{"type":"object","title":"variables","properties":{"variables":{"type":"object","description":"Variables annotation emitted after a tool completes."}},"required":["variables"]},{"type":"object","title":"result","properties":{"result":{"type":"object","description":"Result annotation emitted after a tool completes."}},"required":["result"]}]}}},"required":["id"]}},"required":["tool"]},{"type":"object","title":"connector","properties":{"connector":{"type":"object","description":"A configured connector (wilma-tools) exposed to the model as a single meta-tool named\n after the connector. The model calls the reserved `help` action to learn the callable\n actions, then dispatches an action through the same tool. Progressive disclosure runs\n through the connector tool itself, not the skills catalogue.","properties":{"id":{"type":"string","maxLength":12,"minLength":12,"pattern":"^[0-9A-Za-z]{12}$","description":"Configured connector instance in wilma-tools. Its pinned definition, permission `grant` and authorization govern what the model can discover via `help` and call. There is no version pin here, unlike `AgentTool.toolVersion`: the action catalogue and its schemas are already frozen by the connector's pinned definition, while `grant` and authorization are read live so permission changes apply at once."},"startMessage":{"type":"string","description":"Message spoken when a connector action call starts. Honoured on the voice channel. Mirrors `AgentTool.startMessage`. Not applied to the read-only `help` action. Not consulted once `startMessages` carries any entry."},"startMessages":{"type":"array","items":{"type":"object","description":"One phrase in one language, for a capability that speaks a fixed line on the voice channel.\n Which entry is spoken is decided by the call's current language — the one the call resolved\n to at connect, or the one automatic detection switched to — matched on `language`\n CASE-INSENSITIVELY, the same rule `VoiceBotSpokenPhrase` states for the legacy stack, so a\n code written in either case reaches the same entry on either. Blank `text` means the agent\n says nothing in that language; it does NOT fall back to the language-agnostic member beside\n the list, which would speak one language's text in another language's voice.","properties":{"language":{"type":"string","maxLength":8,"minLength":2,"pattern":"^[a-z]{2,3}(-[A-Z]{2})?$","description":"BCP-47 language tag, optionally with a region subtag: 'en', 'en-US', 'pt-BR'. Deliberately a\n constrained string rather than an enum: the offerable set is a property of the selected\n recogniser and changes with it, so an enumeration here would cap the set at whatever was known\n when the model was released."},"text":{"type":"string"}},"required":["language","text"]},"description":"Forward-compatible per-language connector start messages, mirroring `AgentTool.startMessages`. Accepted and stored so clients can configure connector and tool phrases consistently, but currently ignored at execution time because the voice runtime does not read connector start messages yet. Runtime enablement is tracked in wilma-services docs/todo.md item 8."},"pipeline":{"type":"object","properties":{"type":{"type":"string","description":"How the model waits for a tool and whether it synthesizes a reply from the result.","enum":["blocking_request","blocking_request_guided","async_request","async_request_guided"]},"instructions":{"type":"string","description":"Reply instructions for the *_GUIDED pipeline types. Use {{response}} to reference the tool result."}}},"approval":{"type":"object","description":"Human-in-the-loop confirmation for a tool. Presence requires approval: the agent pauses before running the tool and the session user (whoever controls the session) approves or denies inline.","properties":{"prompt":{"type":"string","description":"Optional message shown in the confirmation prompt. Supports handlebars against agent variables and the pending tool arguments. When omitted, a default prompt is shown."}}},"channels":{"type":"array","items":{"type":"string","description":"A channel an agent can serve (see AgentChannels for enablement). Also the runtime channel recorded by the channel runtimes / wilma-traces. A background task has no delivery surface and therefore no channel.","enum":["voice","chat","assistant"]},"uniqueItems":true,"description":"Channels this capability is offered on. Absent or empty — the default — means every channel the agent serves\n that can honour it. Listing channels narrows that set and never widens it: a `transfer` listed for `chat` is\n still ignored there, because the chat channel has no call to transfer."},"annotations":{"type":"array","items":{"description":"Configuration for annotations emitted after a tool completes.","oneOf":[{"type":"object","title":"tag","properties":{"tag":{"type":"object","description":"Tag annotation emitted after a tool completes.","properties":{"value":{"type":"string","description":"Static handlebars expression resolved against agent variables and helpers."},"applyOnSuccess":{"type":"boolean","description":"Apply when the tool succeeds."},"applyOnFailure":{"type":"boolean","description":"Apply when the tool fails."}}}},"required":["tag"]},{"type":"object","title":"comment","properties":{"comment":{"type":"object","description":"Comment annotation emitted after a tool completes.","properties":{"value":{"type":"string","description":"Static handlebars expression resolved against agent variables and helpers."},"applyOnSuccess":{"type":"boolean","description":"Apply when the tool succeeds."},"applyOnFailure":{"type":"boolean","description":"Apply when the tool fails."}}}},"required":["comment"]},{"type":"object","title":"variables","properties":{"variables":{"type":"object","description":"Variables annotation emitted after a tool completes."}},"required":["variables"]},{"type":"object","title":"result","properties":{"result":{"type":"object","description":"Result annotation emitted after a tool completes."}},"required":["result"]}]}}},"required":["id"]}},"required":["connector"]},{"type":"object","title":"kb","properties":{"kb":{"type":"object","properties":{"knowledgeBaseId":{"type":"string"},"instructions":{"type":"string","maxLength":60000,"minLength":1,"description":"Additional instructions for when the agent should use this knowledge base."},"searchStrategy":{"type":"string","description":"Search algorithm used to retrieve passages.","enum":["bm25","vector","hybrid"]},"searchThreshold":{"type":"string","description":"Minimum similarity a passage needs to be returned. Raising it trades recall for precision.","enum":["low","medium","high"]},"searchLimit":{"type":"number","maximum":50,"minimum":1,"description":"Maximum number of passages this capability returns. Defaults to the knowledge base service's own default (10) when omitted. Keep it low on voice: every passage is prompt the model reads before it can speak."},"channels":{"type":"array","items":{"type":"string","description":"A channel an agent can serve (see AgentChannels for enablement). Also the runtime channel recorded by the channel runtimes / wilma-traces. A background task has no delivery surface and therefore no channel.","enum":["voice","chat","assistant"]},"uniqueItems":true,"description":"Channels this capability is offered on. Absent or empty — the default — means every channel the agent serves\n that can honour it. Listing channels narrows that set and never widens it: a `transfer` listed for `chat` is\n still ignored there, because the chat channel has no call to transfer."},"annotations":{"type":"array","items":{"description":"Configuration for annotations emitted after a tool completes.","oneOf":[{"type":"object","title":"tag","properties":{"tag":{"type":"object","description":"Tag annotation emitted after a tool completes.","properties":{"value":{"type":"string","description":"Static handlebars expression resolved against agent variables and helpers."},"applyOnSuccess":{"type":"boolean","description":"Apply when the tool succeeds."},"applyOnFailure":{"type":"boolean","description":"Apply when the tool fails."}}}},"required":["tag"]},{"type":"object","title":"comment","properties":{"comment":{"type":"object","description":"Comment annotation emitted after a tool completes.","properties":{"value":{"type":"string","description":"Static handlebars expression resolved against agent variables and helpers."},"applyOnSuccess":{"type":"boolean","description":"Apply when the tool succeeds."},"applyOnFailure":{"type":"boolean","description":"Apply when the tool fails."}}}},"required":["comment"]},{"type":"object","title":"variables","properties":{"variables":{"type":"object","description":"Variables annotation emitted after a tool completes."}},"required":["variables"]},{"type":"object","title":"result","properties":{"result":{"type":"object","description":"Result annotation emitted after a tool completes."}},"required":["result"]}]}}}}},"required":["kb"]},{"type":"object","title":"sandbox","properties":{"sandbox":{"description":"Embedded sandbox: shell + file tools backed by an execution environment. The only capability with no `channels` list — being a union it has nowhere to carry one — so it is offered on every channel the agent serves.","oneOf":[{"type":"object","title":"inline","properties":{"inline":{"type":"object","description":"Sandbox configured inline on the agent, without a pre-built snapshot.","properties":{"size":{"type":"string","description":"Compute size of each sandbox launched from the snapshot. Each level maps to a platform-defined vCPU/memory/disk allocation.","enum":["min","small","large","max"]},"network":{"type":"string","description":"Network access policy for the sandbox.","enum":["allow-all","deny-all","allowlist"]},"allowedDomains":{"type":"array","items":{"type":"string","maxLength":253,"minLength":1},"description":"Domains allowed when network = ALLOWLIST."}}}},"required":["inline"]},{"type":"object","title":"snapshot","properties":{"snapshot":{"type":"object","description":"Sandbox launched from a pre-built snapshot managed by the wilma-sandbox service.","properties":{"snapshotId":{"type":"string","maxLength":12,"minLength":12,"description":"Snapshot to launch sandboxes from. Managed in the wilma-sandbox service; must be READY."},"network":{"type":"string","description":"Network access policy for the sandbox.","enum":["allow-all","deny-all","allowlist"]},"allowedDomains":{"type":"array","items":{"type":"string","maxLength":253,"minLength":1},"description":"Domains allowed when network = ALLOWLIST."},"size":{"type":"string","description":"Compute size of each sandbox launched from the snapshot. Each level maps to a platform-defined vCPU/memory/disk allocation.","enum":["min","small","large","max"]}},"required":["snapshotId"]}},"required":["snapshot"]}]}},"required":["sandbox"]},{"type":"object","title":"webSearch","properties":{"webSearch":{"type":"object","description":"Model-native web search. The provider tool is selected from the agent model.","properties":{"contextSize":{"type":"string","description":"Search depth/context hint, mapped per provider.","enum":["low","medium","high"]},"allowedDomains":{"type":"array","items":{"type":"string","maxLength":253,"minLength":1},"description":"Restrict results to these domains where the provider supports it."},"userLocation":{"type":"object","properties":{"country":{"type":"string","description":"ISO 3166-1 alpha-2 country code, e.g. 'US'."},"region":{"type":"string"},"city":{"type":"string"},"timezone":{"type":"string","description":"IANA timezone, e.g. 'Europe/Rome'."}}},"channels":{"type":"array","items":{"type":"string","description":"A channel an agent can serve (see AgentChannels for enablement). Also the runtime channel recorded by the channel runtimes / wilma-traces. A background task has no delivery surface and therefore no channel.","enum":["voice","chat","assistant"]},"uniqueItems":true,"description":"Channels this capability is offered on. Absent or empty — the default — means every channel the agent serves\n that can honour it. Listing channels narrows that set and never widens it: a `transfer` listed for `chat` is\n still ignored there, because the chat channel has no call to transfer."}}}},"required":["webSearch"]},{"type":"object","title":"webFetch","properties":{"webFetch":{"type":"object","description":"Server-side fetch of a URL, returning its readable text/markdown to the model.","properties":{"allowedDomains":{"type":"array","items":{"type":"string","maxLength":253,"minLength":1},"description":"Restrict fetches to these domains. Empty list means any domain is allowed."},"channels":{"type":"array","items":{"type":"string","description":"A channel an agent can serve (see AgentChannels for enablement). Also the runtime channel recorded by the channel runtimes / wilma-traces. A background task has no delivery surface and therefore no channel.","enum":["voice","chat","assistant"]},"uniqueItems":true,"description":"Channels this capability is offered on. Absent or empty — the default — means every channel the agent serves\n that can honour it. Listing channels narrows that set and never widens it: a `transfer` listed for `chat` is\n still ignored there, because the chat channel has no call to transfer."}}}},"required":["webFetch"]},{"type":"object","title":"agent","properties":{"agent":{"type":"object","description":"Invoke another agent as a dedicated execution with its own state. The called agent runs independently and returns its result.","properties":{"agentId":{"type":"string","maxLength":12,"minLength":12},"agentVersion":{"type":"number","minimum":1,"description":"Pin a specific published version of the called agent. When absent, the called agent's deployment configuration selects the version at invocation time."},"access":{"type":"object","description":"Who is allowed to use the agent. There is no view/edit dimension — access solely grants the ability to use the agent.\n\n Where it is applied, per channel:\n - `assistant` — enforced. GetAgent on wilma-assistant refuses an agent the caller is not granted.\n - `voice` — deliberately not applied. Inbound calls are routed by the PBX dialplan, and the data ACL is the\n agent-level `settings.pbxGroupId`, not per-user grants.\n - `chat` — enforced for COLLEAGUES ONLY. wilma-chat checks the sender against this block when the sender\n belongs to the agent's own company. A sender from outside it — a Kite website visitor, an SMS or WhatsApp\n contact — is not governed by this field at all and reaches the agent under every visibility; what exposes\n an agent to them is an administrator wiring it to that surface. A refused colleague receives one short\n refusal message and no turn is started.\n A `groupId` grant is weaker here than on the assistant: this channel reads the sender's group from their\n x-bees user record, which does not always carry one. When EVERY grant on the agent is a `groupId` grant and\n the sender's group is unknown, the sender is PASSED rather than refused — refusing would lock out everyone\n whose record lacks a group, which is a large minority. Adding a `userId` or `department` grant removes that\n waiver and the agent is enforced normally, so use one of those where the restriction must hold on chat.\n (x-bees chat bots are a separate stack with their own company-level `BotAccess`; it is not this field.)\n\n When an agent is invoked as a background task its agent-level access is not used — invocation access is configured\n per reference on the calling agent (see AgentCallCapability.access).","properties":{"visibility":{"type":"string","enum":["explicit","organization","public"]},"grants":{"type":"array","items":{"description":"A user, group or department that access is granted to.","oneOf":[{"type":"object","title":"userId","properties":{"userId":{"type":"string","maxLength":12,"minLength":12}},"required":["userId"]},{"type":"object","title":"groupId","properties":{"groupId":{"type":"string"}},"required":["groupId"]},{"type":"object","title":"department","properties":{"department":{"type":"string","description":"PBX department NAME, as carried on the colleague record (`PbxColleague.department`), not a department id.\n The x-bees directory knows a colleague's department only by name, so matching by name is what lets the check\n read a field the runtime already has instead of resolving the PBX department tree on every request.\n Two consequences: renaming a department in the PBX invalidates every grant naming it, and two departments\n with the same name in different branches of the tree are one value here."}},"required":["department"]}]},"description":"Users, groups and departments granted the ability to use the agent. Applies when visibility is `explicit`."}}},"variables":{"type":"array","items":{"type":"object","properties":{"name":{"type":"string","maxLength":128,"minLength":1,"pattern":"^[a-zA-Z0-9_]+$","description":"Name of the tool variable, e.g.: 'myvar'"},"handler":{"oneOf":[{"type":"object","title":"auto","properties":{"auto":{"type":"object","description":"The model fills this variable itself, from conversation context. Included in the tool's input schema."}},"required":["auto"]},{"type":"object","title":"guided","properties":{"guided":{"type":"object","description":"The model fills this variable, guided by this description. Included in the tool's input schema with the description attached.","properties":{"description":{"type":"string","maxLength":1024,"minLength":1}},"required":["description"]}},"required":["guided"]},{"type":"object","title":"predefined","properties":{"predefined":{"type":"object","description":"A fixed value bound to the variable and injected directly into the tool call. Not exposed to the model (excluded from the tool's input schema). Supports handlebars templates resolved against agent variables and session context (e.g. '{{callerName}}').","properties":{"value":{"type":"string"}},"required":["value"]}},"required":["predefined"]}]}},"required":["name"]}},"channels":{"type":"array","items":{"type":"string","description":"A channel an agent can serve (see AgentChannels for enablement). Also the runtime channel recorded by the channel runtimes / wilma-traces. A background task has no delivery surface and therefore no channel.","enum":["voice","chat","assistant"]},"uniqueItems":true,"description":"Channels this capability is offered on. Absent or empty — the default — means every channel the agent serves\n that can honour it. Listing channels narrows that set and never widens it: a `transfer` listed for `chat` is\n still ignored there, because the chat channel has no call to transfer."}},"required":["agentId"]}},"required":["agent"]},{"type":"object","title":"handover","properties":{"handover":{"type":"object","description":"Hand the conversation over to a human and leave it. Honoured on conversation channels (chat, assistant).","properties":{"name":{"type":"string","description":"Tool name exposed to the model. Auto-generated when omitted."},"description":{"type":"string","description":"Description for the model to decide when to hand over."},"variant":{"type":"string","enum":["agent","service"]},"agent":{"type":"string","maxLength":12,"minLength":12,"description":"Wilma agent to hand over to when variant = AGENT."},"agentVersion":{"type":"number","minimum":1,"description":"Pin a specific published version of the target agent when variant = AGENT. When absent, the target's deployment configuration selects the version at handover time."},"service":{"type":"string","description":"x-bees service id to hand over to when variant = SERVICE."},"silent":{"type":"boolean","description":"Hand over without posting a visible message to the user."},"pipeline":{"type":"object","properties":{"type":{"type":"string","enum":["handover","reply_and_handover","reply_instructed_and_handover"]},"instructions":{"type":"string","description":"Instructions for the model reply when type is REPLY_INSTRUCTED_AND_HANDOVER."}}},"channels":{"type":"array","items":{"type":"string","description":"A channel an agent can serve (see AgentChannels for enablement). Also the runtime channel recorded by the channel runtimes / wilma-traces. A background task has no delivery surface and therefore no channel.","enum":["voice","chat","assistant"]},"uniqueItems":true,"description":"Channels this capability is offered on. Absent or empty — the default — means every channel the agent serves\n that can honour it. Listing channels narrows that set and never widens it: a `transfer` listed for `chat` is\n still ignored there, because the chat channel has no call to transfer."},"annotations":{"type":"array","items":{"description":"Configuration for annotations emitted after a tool completes.","oneOf":[{"type":"object","title":"tag","properties":{"tag":{"type":"object","description":"Tag annotation emitted after a tool completes.","properties":{"value":{"type":"string","description":"Static handlebars expression resolved against agent variables and helpers."},"applyOnSuccess":{"type":"boolean","description":"Apply when the tool succeeds."},"applyOnFailure":{"type":"boolean","description":"Apply when the tool fails."}}}},"required":["tag"]},{"type":"object","title":"comment","properties":{"comment":{"type":"object","description":"Comment annotation emitted after a tool completes.","properties":{"value":{"type":"string","description":"Static handlebars expression resolved against agent variables and helpers."},"applyOnSuccess":{"type":"boolean","description":"Apply when the tool succeeds."},"applyOnFailure":{"type":"boolean","description":"Apply when the tool fails."}}}},"required":["comment"]},{"type":"object","title":"variables","properties":{"variables":{"type":"object","description":"Variables annotation emitted after a tool completes."}},"required":["variables"]},{"type":"object","title":"result","properties":{"result":{"type":"object","description":"Result annotation emitted after a tool completes."}},"required":["result"]}]}}}}},"required":["handover"]},{"type":"object","title":"delegation","properties":{"delegation":{"type":"object","description":"Delegate the conversation to another agent (max 3 transitions per turn). One capability entry per delegate target.","properties":{"name":{"type":"string","description":"Tool name exposed to the model. Auto-generated when omitted."},"description":{"type":"string","description":"Description for the model to decide when to delegate."},"id":{"type":"string","maxLength":12,"minLength":12,"description":"Agent to delegate to."},"agentVersion":{"type":"number","minimum":1,"description":"Pin a specific published version of the delegate agent. When absent, the delegate's deployment configuration selects the version at delegation time."},"channels":{"type":"array","items":{"type":"string","description":"A channel an agent can serve (see AgentChannels for enablement). Also the runtime channel recorded by the channel runtimes / wilma-traces. A background task has no delivery surface and therefore no channel.","enum":["voice","chat","assistant"]},"uniqueItems":true,"description":"Channels this capability is offered on. Absent or empty — the default — means every channel the agent serves\n that can honour it. Listing channels narrows that set and never widens it: a `transfer` listed for `chat` is\n still ignored there, because the chat channel has no call to transfer."},"annotations":{"type":"array","items":{"description":"Configuration for annotations emitted after a tool completes.","oneOf":[{"type":"object","title":"tag","properties":{"tag":{"type":"object","description":"Tag annotation emitted after a tool completes.","properties":{"value":{"type":"string","description":"Static handlebars expression resolved against agent variables and helpers."},"applyOnSuccess":{"type":"boolean","description":"Apply when the tool succeeds."},"applyOnFailure":{"type":"boolean","description":"Apply when the tool fails."}}}},"required":["tag"]},{"type":"object","title":"comment","properties":{"comment":{"type":"object","description":"Comment annotation emitted after a tool completes.","properties":{"value":{"type":"string","description":"Static handlebars expression resolved against agent variables and helpers."},"applyOnSuccess":{"type":"boolean","description":"Apply when the tool succeeds."},"applyOnFailure":{"type":"boolean","description":"Apply when the tool fails."}}}},"required":["comment"]},{"type":"object","title":"variables","properties":{"variables":{"type":"object","description":"Variables annotation emitted after a tool completes."}},"required":["variables"]},{"type":"object","title":"result","properties":{"result":{"type":"object","description":"Result annotation emitted after a tool completes."}},"required":["result"]}]}}},"required":["id"]}},"required":["delegation"]},{"type":"object","title":"transfer","properties":{"transfer":{"type":"object","description":"Transfer the call to a PBX destination. Honoured on the voice channel. One capability entry per transfer target.","properties":{"name":{"type":"string","description":"Tool name exposed to the model. Auto-generated when omitted."},"description":{"type":"string","description":"Description for the model to decide when to transfer."},"variant":{"type":"string","enum":["predefined","dynamic","directory"]},"context":{"type":"string","description":"Routing context for PREDEFINED transfers."},"extension":{"type":"string","description":"Extension or phone number for PREDEFINED transfers."},"pipeline":{"type":"object","properties":{"type":{"type":"string","enum":["transfer","reply_and_transfer","reply_instructed_and_transfer"]},"instructions":{"type":"string","description":"Instructions for the model reply when type is REPLY_INSTRUCTED_AND_TRANSFER."}}},"channels":{"type":"array","items":{"type":"string","description":"A channel an agent can serve (see AgentChannels for enablement). Also the runtime channel recorded by the channel runtimes / wilma-traces. A background task has no delivery surface and therefore no channel.","enum":["voice","chat","assistant"]},"uniqueItems":true,"description":"Channels this capability is offered on. Absent or empty — the default — means every channel the agent serves\n that can honour it. Listing channels narrows that set and never widens it: a `transfer` listed for `chat` is\n still ignored there, because the chat channel has no call to transfer."},"annotations":{"type":"array","items":{"description":"Configuration for annotations emitted after a tool completes.","oneOf":[{"type":"object","title":"tag","properties":{"tag":{"type":"object","description":"Tag annotation emitted after a tool completes.","properties":{"value":{"type":"string","description":"Static handlebars expression resolved against agent variables and helpers."},"applyOnSuccess":{"type":"boolean","description":"Apply when the tool succeeds."},"applyOnFailure":{"type":"boolean","description":"Apply when the tool fails."}}}},"required":["tag"]},{"type":"object","title":"comment","properties":{"comment":{"type":"object","description":"Comment annotation emitted after a tool completes.","properties":{"value":{"type":"string","description":"Static handlebars expression resolved against agent variables and helpers."},"applyOnSuccess":{"type":"boolean","description":"Apply when the tool succeeds."},"applyOnFailure":{"type":"boolean","description":"Apply when the tool fails."}}}},"required":["comment"]},{"type":"object","title":"variables","properties":{"variables":{"type":"object","description":"Variables annotation emitted after a tool completes."}},"required":["variables"]},{"type":"object","title":"result","properties":{"result":{"type":"object","description":"Result annotation emitted after a tool completes."}},"required":["result"]}]}}}}},"required":["transfer"]},{"type":"object","title":"suggestions","properties":{"suggestions":{"type":"object","description":"Emit suggested replies for human operators. Honoured on the chat channel.","properties":{"name":{"type":"string","description":"Tool name. Auto-generated when omitted."},"instructions":{"type":"string","maxLength":2000,"minLength":1,"description":"Steers what the suggestions say. Appended to the platform's own generation prompt rather than\n replacing it, so the shape of a suggestion — short, tappable, a plausible next thing to send —\n stays the platform's to keep, and this narrows the subject: 'offer a callback and a link to the\n status page, never a discount'.\n\n NOT a tool description, unlike the `description` every other capability carries: suggestions are\n not a tool the model chooses to call. They are generated from a reply that has already been\n written, so there is no decision to describe — only the generation to steer.\n\n Absent means the platform's prompt alone."},"channels":{"type":"array","items":{"type":"string","description":"A channel an agent can serve (see AgentChannels for enablement). Also the runtime channel recorded by the channel runtimes / wilma-traces. A background task has no delivery surface and therefore no channel.","enum":["voice","chat","assistant"]},"uniqueItems":true,"description":"Channels this capability is offered on. Absent or empty — the default — means every channel the agent serves\n that can honour it. Listing channels narrows that set and never widens it: a `transfer` listed for `chat` is\n still ignored there, because the chat channel has no call to transfer."},"annotations":{"type":"array","items":{"description":"Configuration for annotations emitted after a tool completes.","oneOf":[{"type":"object","title":"tag","properties":{"tag":{"type":"object","description":"Tag annotation emitted after a tool completes.","properties":{"value":{"type":"string","description":"Static handlebars expression resolved against agent variables and helpers."},"applyOnSuccess":{"type":"boolean","description":"Apply when the tool succeeds."},"applyOnFailure":{"type":"boolean","description":"Apply when the tool fails."}}}},"required":["tag"]},{"type":"object","title":"comment","properties":{"comment":{"type":"object","description":"Comment annotation emitted after a tool completes.","properties":{"value":{"type":"string","description":"Static handlebars expression resolved against agent variables and helpers."},"applyOnSuccess":{"type":"boolean","description":"Apply when the tool succeeds."},"applyOnFailure":{"type":"boolean","description":"Apply when the tool fails."}}}},"required":["comment"]},{"type":"object","title":"variables","properties":{"variables":{"type":"object","description":"Variables annotation emitted after a tool completes."}},"required":["variables"]},{"type":"object","title":"result","properties":{"result":{"type":"object","description":"Result annotation emitted after a tool completes."}},"required":["result"]}]}}}}},"required":["suggestions"]},{"type":"object","title":"hangup","properties":{"hangup":{"type":"object","description":"Let the model end the call. Honoured on the voice channel.","properties":{"name":{"type":"string","description":"Tool name exposed to the model. Auto-generated when omitted."},"description":{"type":"string","description":"Description for the model to decide when to hang up."},"pipeline":{"type":"object","properties":{"type":{"type":"string","enum":["hangup","reply_and_hangup","reply_instructed_and_hangup"]},"instructions":{"type":"string","description":"Instructions for the model reply when type is REPLY_INSTRUCTED_AND_HANGUP."}}},"channels":{"type":"array","items":{"type":"string","description":"A channel an agent can serve (see AgentChannels for enablement). Also the runtime channel recorded by the channel runtimes / wilma-traces. A background task has no delivery surface and therefore no channel.","enum":["voice","chat","assistant"]},"uniqueItems":true,"description":"Channels this capability is offered on. Absent or empty — the default — means every channel the agent serves\n that can honour it. Listing channels narrows that set and never widens it: a `transfer` listed for `chat` is\n still ignored there, because the chat channel has no call to transfer."},"annotations":{"type":"array","items":{"description":"Configuration for annotations emitted after a tool completes.","oneOf":[{"type":"object","title":"tag","properties":{"tag":{"type":"object","description":"Tag annotation emitted after a tool completes.","properties":{"value":{"type":"string","description":"Static handlebars expression resolved against agent variables and helpers."},"applyOnSuccess":{"type":"boolean","description":"Apply when the tool succeeds."},"applyOnFailure":{"type":"boolean","description":"Apply when the tool fails."}}}},"required":["tag"]},{"type":"object","title":"comment","properties":{"comment":{"type":"object","description":"Comment annotation emitted after a tool completes.","properties":{"value":{"type":"string","description":"Static handlebars expression resolved against agent variables and helpers."},"applyOnSuccess":{"type":"boolean","description":"Apply when the tool succeeds."},"applyOnFailure":{"type":"boolean","description":"Apply when the tool fails."}}}},"required":["comment"]},{"type":"object","title":"variables","properties":{"variables":{"type":"object","description":"Variables annotation emitted after a tool completes."}},"required":["variables"]},{"type":"object","title":"result","properties":{"result":{"type":"object","description":"Result annotation emitted after a tool completes."}},"required":["result"]}]}}}}},"required":["hangup"]},{"type":"object","title":"wait","properties":{"wait":{"type":"object","description":"Let the model hold the call while it works (e.g. a lookup), keeping the caller on the line. Honoured on the voice channel.","properties":{"name":{"type":"string","description":"Tool name exposed to the model. Auto-generated when omitted."},"description":{"type":"string","description":"Description for the model to decide when to wait."},"message":{"type":"string","description":"Message spoken when entering the wait state. Spoken in every language of the call while `messages` is absent or empty; not consulted once `messages` carries any entry."},"messages":{"type":"array","items":{"type":"object","description":"One phrase in one language, for a capability that speaks a fixed line on the voice channel.\n Which entry is spoken is decided by the call's current language — the one the call resolved\n to at connect, or the one automatic detection switched to — matched on `language`\n CASE-INSENSITIVELY, the same rule `VoiceBotSpokenPhrase` states for the legacy stack, so a\n code written in either case reaches the same entry on either. Blank `text` means the agent\n says nothing in that language; it does NOT fall back to the language-agnostic member beside\n the list, which would speak one language's text in another language's voice.","properties":{"language":{"type":"string","maxLength":8,"minLength":2,"pattern":"^[a-z]{2,3}(-[A-Z]{2})?$","description":"BCP-47 language tag, optionally with a region subtag: 'en', 'en-US', 'pt-BR'. Deliberately a\n constrained string rather than an enum: the offerable set is a property of the selected\n recogniser and changes with it, so an enumeration here would cap the set at whatever was known\n when the model was released."},"text":{"type":"string"}},"required":["language","text"]},"description":"Per-language wait messages. Same rule as `AgentTool.startMessages`: the entry for the call's current language is spoken, a language with no entry is silent, and with no effective speech configuration `message` is spoken."},"channels":{"type":"array","items":{"type":"string","description":"A channel an agent can serve (see AgentChannels for enablement). Also the runtime channel recorded by the channel runtimes / wilma-traces. A background task has no delivery surface and therefore no channel.","enum":["voice","chat","assistant"]},"uniqueItems":true,"description":"Channels this capability is offered on. Absent or empty — the default — means every channel the agent serves\n that can honour it. Listing channels narrows that set and never widens it: a `transfer` listed for `chat` is\n still ignored there, because the chat channel has no call to transfer."},"annotations":{"type":"array","items":{"description":"Configuration for annotations emitted after a tool completes.","oneOf":[{"type":"object","title":"tag","properties":{"tag":{"type":"object","description":"Tag annotation emitted after a tool completes.","properties":{"value":{"type":"string","description":"Static handlebars expression resolved against agent variables and helpers."},"applyOnSuccess":{"type":"boolean","description":"Apply when the tool succeeds."},"applyOnFailure":{"type":"boolean","description":"Apply when the tool fails."}}}},"required":["tag"]},{"type":"object","title":"comment","properties":{"comment":{"type":"object","description":"Comment annotation emitted after a tool completes.","properties":{"value":{"type":"string","description":"Static handlebars expression resolved against agent variables and helpers."},"applyOnSuccess":{"type":"boolean","description":"Apply when the tool succeeds."},"applyOnFailure":{"type":"boolean","description":"Apply when the tool fails."}}}},"required":["comment"]},{"type":"object","title":"variables","properties":{"variables":{"type":"object","description":"Variables annotation emitted after a tool completes."}},"required":["variables"]},{"type":"object","title":"result","properties":{"result":{"type":"object","description":"Result annotation emitted after a tool completes."}},"required":["result"]}]}}}}},"required":["wait"]}]},"description":"Everything the agent can do. When a `workflow` is set these are the capabilities in scope at every step, and a step may add to them or narrow them with its own list of the same shape."},"variables":{"type":"array","items":{"type":"object","properties":{"name":{"type":"string","maxLength":128,"minLength":1,"pattern":"^[a-zA-Z0-9_]+$"},"type":{"type":"string","enum":["string","number","boolean","string_array","number_array"]},"optional":{"type":"boolean"},"description":{"type":"string","description":"What the variable means and how it should be filled."}},"required":["name","optional","type"]}}},"required":["instructions"]}},"required":["ai"]},{"type":"object","title":"webhook","properties":{"webhook":{"type":"object","description":"Bring-your-own backend over HTTPS. The platform POSTs call events to the URL; the backend\nanswers out of band through the channel's command API. The response body is not read.","properties":{"url":{"type":"string"},"secret":{"type":"object","description":"A reference to a stored secret, held by other services in place of the value.\nResolved server-to-server via GetSecretValue at execution time — always to the current value.","properties":{"id":{"type":"string","maxLength":8,"minLength":8,"pattern":"^[0-9A-Za-z]{8}$","description":"Unique identifier of a secret."}},"required":["id"]},"timeout":{"type":"number","default":10000,"maximum":120000,"minimum":5000,"description":"Timeout of one delivery, in milliseconds."}},"required":["url"]}},"required":["webhook"]},{"type":"object","title":"sqs","properties":{"sqs":{"type":"object","description":"Bring-your-own backend over SQS. The platform sends call events to the queue; the backend\nanswers out of band through the channel's command API.","properties":{"url":{"type":"string"},"key":{"type":"string"},"secret":{"type":"object","description":"A reference to a stored secret, held by other services in place of the value.\nResolved server-to-server via GetSecretValue at execution time — always to the current value.","properties":{"id":{"type":"string","maxLength":8,"minLength":8,"pattern":"^[0-9A-Za-z]{8}$","description":"Unique identifier of a secret."}},"required":["id"]}},"required":["key","url"]}},"required":["sqs"]}]},"settings":{"type":"object","description":"Agent-level settings shared across all channels.","properties":{"pbxGroupId":{"type":"string","description":"PBX group used for data ACL (e.g. Sales Intelligence lookups)."}}},"access":{"type":"object","description":"Who is allowed to use the agent. There is no view/edit dimension — access solely grants the ability to use the agent.\n\n Where it is applied, per channel:\n - `assistant` — enforced. GetAgent on wilma-assistant refuses an agent the caller is not granted.\n - `voice` — deliberately not applied. Inbound calls are routed by the PBX dialplan, and the data ACL is the\n agent-level `settings.pbxGroupId`, not per-user grants.\n - `chat` — enforced for COLLEAGUES ONLY. wilma-chat checks the sender against this block when the sender\n belongs to the agent's own company. A sender from outside it — a Kite website visitor, an SMS or WhatsApp\n contact — is not governed by this field at all and reaches the agent under every visibility; what exposes\n an agent to them is an administrator wiring it to that surface. A refused colleague receives one short\n refusal message and no turn is started.\n A `groupId` grant is weaker here than on the assistant: this channel reads the sender's group from their\n x-bees user record, which does not always carry one. When EVERY grant on the agent is a `groupId` grant and\n the sender's group is unknown, the sender is PASSED rather than refused — refusing would lock out everyone\n whose record lacks a group, which is a large minority. Adding a `userId` or `department` grant removes that\n waiver and the agent is enforced normally, so use one of those where the restriction must hold on chat.\n (x-bees chat bots are a separate stack with their own company-level `BotAccess`; it is not this field.)\n\n When an agent is invoked as a background task its agent-level access is not used — invocation access is configured\n per reference on the calling agent (see AgentCallCapability.access).","properties":{"visibility":{"type":"string","enum":["explicit","organization","public"]},"grants":{"type":"array","items":{"description":"A user, group or department that access is granted to.","oneOf":[{"type":"object","title":"userId","properties":{"userId":{"type":"string","maxLength":12,"minLength":12}},"required":["userId"]},{"type":"object","title":"groupId","properties":{"groupId":{"type":"string"}},"required":["groupId"]},{"type":"object","title":"department","properties":{"department":{"type":"string","description":"PBX department NAME, as carried on the colleague record (`PbxColleague.department`), not a department id.\n The x-bees directory knows a colleague's department only by name, so matching by name is what lets the check\n read a field the runtime already has instead of resolving the PBX department tree on every request.\n Two consequences: renaming a department in the PBX invalidates every grant naming it, and two departments\n with the same name in different branches of the tree are one value here."}},"required":["department"]}]},"description":"Users, groups and departments granted the ability to use the agent. Applies when visibility is `explicit`."}}}},"required":["channels","engine","name"]}}}},"/v2/agents/agents-names":{"get":{"operationId":"ListAgentsNames","summary":"","description":"Retrieves a lightweight list of agent names and basic information for the specified company.\n\n### Allowed Auth Scopes\n\n- wilma-agents:ListAgentsNames\n- wilma-agents:read","parameters":[{"name":"company","in":"query","required":false,"schema":{"type":"string","maxLength":30,"minLength":3}},{"name":"channel","in":"query","required":false,"schema":{"type":"string","description":"A channel an agent can serve (see AgentChannels for enablement). Also the runtime channel recorded by the channel runtimes / wilma-traces. A background task has no delivery surface and therefore no channel.","enum":["voice","chat","assistant"]},"description":"A channel an agent can serve (see AgentChannels for enablement). Also the runtime channel recorded by the channel runtimes / wilma-traces. A background task has no delivery surface and therefore no channel."}]}},"/v2/agents/agents/{agentId}":{"get":{"operationId":"GetAgent","summary":"","description":"Retrieves an agent, including its draft definition and version/rollout state.\n\n### Allowed Auth Scopes\n\n- wilma-agents:GetAgent\n- wilma-agents:read","parameters":[{"name":"agentId","in":"path","required":true,"schema":{"type":"string","maxLength":12,"minLength":12}},{"name":"company","in":"query","required":false,"schema":{"type":"string","maxLength":30,"minLength":3}}]},"put":{"operationId":"UpdateAgent","summary":"","description":"Updates the draft definition of an agent. Does not affect served traffic until the draft is published.\n\n### Allowed Auth Scopes\n\n- wilma-agents:UpdateAgent","parameters":[{"name":"agentId","in":"path","required":true,"schema":{"type":"string","maxLength":12,"minLength":12}},{"name":"company","in":"query","required":false,"schema":{"type":"string","maxLength":30,"minLength":3}}],"requestBody":{"required":true,"schema":{"type":"object","description":"The complete, versionable configuration of an agent. This is what gets snapshotted when a version is published.","properties":{"name":{"type":"string","maxLength":255,"minLength":3},"description":{"type":"string","maxLength":1000,"minLength":1},"category":{"type":"string","maxLength":128,"minLength":1,"description":"Category of the agent, e.g.: 'Support'."},"picture":{"type":"string","description":"Avatar URL."},"channels":{"type":"object","description":"The channels an agent serves and their channel-specific settings. Presence of a member enables that channel. Empty means the agent serves no channel: it is not reachable on its own and runs only as an automation's task or when another agent calls it. Enabled channels are mirrored as ChannelType values on AgentInfo.","properties":{"voice":{"type":"object","description":"Voice channel settings. Note: a voice agent's AgentAccess.grants are ignored — inbound calls are routed by the PBX dialplan and data ACL is the agent-level `settings.pbxGroupId`, not per-user grants.","properties":{"greeting":{"type":"string","description":"Message spoken when the call starts."},"backgroundSound":{"type":"object","description":"Ambient background audio played under the call.\n\n A SETTINGS OBJECT like `interruptions` and `endCall`, rather than a loose name on the channel:\n it is one switchable call behaviour, it reads the same way as its siblings, and the volume and\n fade controls the console will want next have somewhere to go.\n\n ABSENT MEANS OFF — a bot that never touches it behaves exactly as every call did before this\n setting existed, and the PBX needs no new case.","properties":{"enabled":{"type":"boolean","description":"Whether ambient background audio plays under the call."},"sound":{"type":"string","maxLength":64,"minLength":1,"pattern":"^[^\\r\\n]+$","description":"Music-on-hold class the PBX plays. Absent with `enabled` true leaves the choice of class to the PBX."}},"required":["enabled"]},"speech":{"type":"object","description":"Speech configuration for the voice channel. When present it WINS over the dialplan's `voice` and\n `initialMessage` connect parameters; `language` is the single exception, because it is the\n channel's own input signal and the feature has no starting point without it.","properties":{"languages":{"type":"array","items":{"type":"object","description":"One configured language: its voice, its initial phrase and its pronunciation rules.","properties":{"code":{"type":"string","maxLength":8,"minLength":2,"pattern":"^[a-z]{2,3}(-[A-Z]{2})?$","description":"BCP-47 code with locale, e.g. 'en-US', 'it-IT'."},"voice":{"type":"string","maxLength":1024,"minLength":1,"pattern":"^[a-z0-9][a-z0-9_-]*://[a-zA-Z0-9._~/-]+(\\?[a-zA-Z0-9._~%&=+:/-]*)?$","description":"Voice this language speaks with, as a URI — provider, voice id, synthesis parameters and,\n where the company uses its own provider account, an `apiKey=secrets:{secretId}` reference.\n See VoiceUri.\n\n PER LANGUAGE, and that is the whole reason the member is here rather than on the settings\n above: provider quality is not uniform across languages, a voice id belongs to exactly one\n provider, and a bot-wide provider made changing it invalidate every configured voice at\n once."},"greeting":{"type":"string","description":"Initial phrase spoken when the call starts in this language. Absent or empty means\n the agent says nothing and waits — it does NOT fall back to VoiceChannelSettings.greeting,\n which would speak one language's text in another language's voice."},"isDefault":{"type":"boolean","description":"Marks the language a caller hears when their own matches nothing configured.\n\n THE FULL RESOLUTION ORDER, since a client author reading only this contract cannot\n otherwise find it: an exact match on the call's language, then a match on its primary\n subtag (`fr` finds `fr-FR`), then the language marked here, then a language whose\n primary subtag is `en`, then the first entry in the list.\n\n Marked is consulted BEFORE the English step, so a record with none marked resolves\n exactly as it did before this member existed. English is deliberate rather than\n arbitrary — it is the language a caller the agent was not built for is likeliest to\n understand — and the last step exists only to keep the rule total, which is why list\n order should not be treated as configuration.\n\n At most one language may carry it. A record with two — reachable through the API or\n the CLI, where no form prevents it — resolves to the first marked one in list order,\n because refusing a call over configuration drift is worse than serving it and saying\n so in the trace."},"pronunciations":{"type":"array","items":{"type":"object","description":"A pronunciation rule for one phrase. Belongs to a LANGUAGE, not to the agent: 'Wildix' is\n respelled differently for an Italian and a German voice, so one list shared ACROSS languages\n would fight itself. That is why the rules hang off the language and nowhere else — there is no\n company-wide dictionary above them to inherit from, or to disagree with.","properties":{"phrase":{"type":"string","maxLength":200,"description":"The phrase as it appears in the text, e.g. 'Wildix'. Bounded because every rule is compiled into ONE regular expression — see the list below."},"as":{"type":"string","maxLength":200,"description":"Respelling (ALIAS) or phonetic transcription (IPA), e.g. 'Wil-dix'."},"encoding":{"type":"string","description":"How the `as` member of a pronunciation rule is written.","enum":["alias","ipa"]}},"required":["as","phrase"]},"maxItems":200,"description":"The rules this language is spoken with. THE WHOLE SET rather than an override layer: an\n agent's respellings are stored here and read from here, so what this list says is what the\n caller hears.\n\n THERE IS NOTHING ABOVE IT. An earlier draft of this contract carried a company-wide\n dictionary per language that every agent speaking it inherited, with a merge order over this\n member and an identity rule ('Wildix' -> 'Wildix') for declining an inherited respelling.\n It bought correcting a handful of names in one place, and cost a second store, a merge on\n every synthesis path, a screen of its own, and a reader who could not tell from an agent\n what that agent would actually say. Per language per agent is where the divergence that\n matters already lives."}},"required":["code","voice"]},"maxItems":10,"minItems":1,"description":"Configured languages. The @length bound is a STORAGE ceiling, not a promise about what a\n recogniser accepts at once: automatic detection here is opt-in (`transcription.autoDetect`),\n and once a caller turns it on, the service does not clamp the resulting candidate list to a\n recogniser's own practical limit today (Amazon Transcribe's documented recommendation for\n streaming identification is five) — a caller who both opts in and configures more than a\n recogniser accepts should expect degraded identification or a rejected stream, not a refusal\n from this contract. With detection off, only the single resolved language is ever sent, so\n the ceiling never becomes a candidate-list problem at all."},"greetEveryLanguage":{"type":"boolean","description":"Speak the first message of every configured language in turn when the call is answered, each\n in the voice, provider account and pronunciation rules of its own language, instead of only the\n language the call resolved to. Languages whose first message is empty, and languages the PBX\n synthesizes itself, are skipped. Absent means off. Requires at least two languages carrying a\n first message; below that the runtime speaks only the resolved language's phrase, and the\n console clamps the member on save."},"transcription":{"type":"object","description":"Speech-to-text configuration: which recogniser listens to the caller, and whether it identifies the language while it does.","properties":{"engine":{"type":"string","maxLength":1024,"minLength":1,"pattern":"^[a-z0-9][a-z0-9_-]*://[a-zA-Z0-9._~/-]+(\\?[a-zA-Z0-9._~%&=+:/-]*)?$","description":"Recogniser to transcribe the caller with. Absent resolves to the platform default ('aws://transcribe' today), resolved by the server so exactly one component decides it."},"autoDetect":{"type":"boolean","description":"Identify the caller's language per utterance and switch voice and reply language mid-call.\n\n HERE RATHER THAN ON `VoiceSpeechSettings`, because identifying a language is something the\n RECOGNISER does: it becomes identify-multiple-languages plus language-options on the\n recognition stream, and nothing about synthesis reads it.\n\n Requires at least two configured languages and a recogniser that can identify them; the\n server clamps it to false otherwise, because forwarding true with a single candidate makes\n Amazon reject the stream outright."}}}},"required":["languages"]},"interruptions":{"type":"object","properties":{"enabled":{"type":"boolean","description":"Whether the caller can interrupt the agent while it is speaking."},"minWords":{"type":"number","maximum":10,"minimum":1,"description":"Minimum number of caller words that count as an interruption."}},"required":["enabled"]},"silenceTimeout":{"type":"object","properties":{"seconds":{"type":"number","maximum":600,"minimum":1,"description":"Seconds of caller silence before the action is taken."},"action":{"oneOf":[{"type":"object","title":"hangup","properties":{"hangup":{"type":"object","properties":{"message":{"type":"string","description":"Message played before hanging up. Played in every language of the call while `messages` is absent or empty; not consulted once `messages` carries any entry."},"messages":{"type":"array","items":{"type":"object","description":"One phrase in one language, for a capability that speaks a fixed line on the voice channel.\n Which entry is spoken is decided by the call's current language — the one the call resolved\n to at connect, or the one automatic detection switched to — matched on `language`\n CASE-INSENSITIVELY, the same rule `VoiceBotSpokenPhrase` states for the legacy stack, so a\n code written in either case reaches the same entry on either. Blank `text` means the agent\n says nothing in that language; it does NOT fall back to the language-agnostic member beside\n the list, which would speak one language's text in another language's voice.","properties":{"language":{"type":"string","maxLength":8,"minLength":2,"pattern":"^[a-z]{2,3}(-[A-Z]{2})?$","description":"BCP-47 language tag, optionally with a region subtag: 'en', 'en-US', 'pt-BR'. Deliberately a\n constrained string rather than an enum: the offerable set is a property of the selected\n recogniser and changes with it, so an enumeration here would cap the set at whatever was known\n when the model was released."},"text":{"type":"string"}},"required":["language","text"]},"description":"Per-language messages played before hanging up. Same rule as `AgentTool.startMessages`: the entry for the call's current language is played, a language with no entry is silent, and with no effective speech configuration `message` is played."}}}},"required":["hangup"]},{"type":"object","title":"transfer","properties":{"transfer":{"type":"object","properties":{"name":{"type":"string","description":"Human readable name the model can refer to, e.g. 'Billing department'."},"description":{"type":"string","description":"When this target should be chosen."},"context":{"type":"string"},"extension":{"type":"string"}},"required":["context","extension","name"]}},"required":["transfer"]}]}},"required":["action","seconds"]},"maxDuration":{"type":"object","properties":{"seconds":{"type":"number","maximum":14400,"minimum":10,"description":"Maximum call duration in seconds before the action is taken."},"action":{"oneOf":[{"type":"object","title":"hangup","properties":{"hangup":{"type":"object","properties":{"message":{"type":"string","description":"Message played before hanging up. Played in every language of the call while `messages` is absent or empty; not consulted once `messages` carries any entry."},"messages":{"type":"array","items":{"type":"object","description":"One phrase in one language, for a capability that speaks a fixed line on the voice channel.\n Which entry is spoken is decided by the call's current language — the one the call resolved\n to at connect, or the one automatic detection switched to — matched on `language`\n CASE-INSENSITIVELY, the same rule `VoiceBotSpokenPhrase` states for the legacy stack, so a\n code written in either case reaches the same entry on either. Blank `text` means the agent\n says nothing in that language; it does NOT fall back to the language-agnostic member beside\n the list, which would speak one language's text in another language's voice.","properties":{"language":{"type":"string","maxLength":8,"minLength":2,"pattern":"^[a-z]{2,3}(-[A-Z]{2})?$","description":"BCP-47 language tag, optionally with a region subtag: 'en', 'en-US', 'pt-BR'. Deliberately a\n constrained string rather than an enum: the offerable set is a property of the selected\n recogniser and changes with it, so an enumeration here would cap the set at whatever was known\n when the model was released."},"text":{"type":"string"}},"required":["language","text"]},"description":"Per-language messages played before hanging up. Same rule as `AgentTool.startMessages`: the entry for the call's current language is played, a language with no entry is silent, and with no effective speech configuration `message` is played."}}}},"required":["hangup"]},{"type":"object","title":"transfer","properties":{"transfer":{"type":"object","properties":{"name":{"type":"string","description":"Human readable name the model can refer to, e.g. 'Billing department'."},"description":{"type":"string","description":"When this target should be chosen."},"context":{"type":"string"},"extension":{"type":"string"}},"required":["context","extension","name"]}},"required":["transfer"]}]}},"required":["action","seconds"]},"endCall":{"type":"object","properties":{"enabled":{"type":"boolean"},"phrases":{"type":"string","description":"Comma-separated phrases that end the call, e.g. 'goodbye,bye'."}},"required":["enabled"]},"callerMetadata":{"type":"object","description":"Caller context injected into the model prompt.","properties":{"callerName":{"type":"boolean","description":"Include the caller name. Defaults to true."},"callerNumber":{"type":"boolean","description":"Include the caller phone number. Defaults to true."},"callerEmail":{"type":"boolean","description":"Include the caller email when known. Defaults to false."},"callerCompany":{"type":"boolean","description":"Include the caller company when known. Defaults to false."},"time":{"type":"boolean","description":"Include the current time, rendered in the agent's `engine.ai.timeZone`. Defaults to false."}}}}},"chat":{"type":"object","properties":{"discoverable":{"type":"boolean","description":"Whether users can find and add this agent to conversations."},"sessionTimeoutMinutes":{"type":"number","maximum":120,"minimum":15,"description":"Minutes a conversation can stay idle before the next message starts a new session. Omit to start a new session at the end of the agent's local day (`engine.ai.timeZone`). Allowed values: 15, 30, 60, 120."},"verbosity":{"type":"string","description":"How much of a chat turn x-bees shows while the agent works. SMS and WhatsApp always send one final text.","enum":["none","simple","full"]}}},"assistant":{"type":"object","description":"Assistant channel settings. Presence enables the in-app assistant surface; audio remains unavailable unless its separate settings are present and enabled.","properties":{"default":{"type":"boolean","description":"Offer this agent as the default assistant for users covered by the agent access settings."},"ui":{"type":"object","description":"How the agent presents itself in the assistant UI: what the user reads before typing anything, and what they can start from without typing at all. Everything here is presentation — it never changes what the agent can do.","properties":{"welcome":{"type":"object","description":"Greeting shown on an empty thread, above the quick commands.","properties":{"title":{"type":"string","maxLength":80,"minLength":1,"description":"Heading, e.g. 'How can I help?'. Rendered on one line."},"subtitle":{"type":"string","maxLength":200,"minLength":1,"description":"Line under the heading, saying what this agent is for, e.g. 'Ask about your conversations, accounts and calls.'."}}},"commands":{"type":"array","items":{"type":"object","description":"A starter prompt offered as a card on an empty thread. Picking it sends `prompt` as the user's first message, so the user never types it.","properties":{"title":{"type":"string","maxLength":60,"minLength":1,"description":"Card label, e.g. 'Summarize my day'. Rendered on one line and truncated when it does not fit."},"description":{"type":"string","maxLength":120,"minLength":1,"description":"Optional line under the label. Also one line, so keep it to a short clarification of what the command does."},"prompt":{"type":"string","maxLength":2000,"minLength":1,"description":"The message actually sent when the card is picked. Usually longer and more explicit than the label, since it is what the model reads."}},"required":["prompt","title"]},"maxItems":8,"description":"Cards offered under the welcome message on an empty thread, in the order given. Empty or omitted shows no cards."},"placeholder":{"type":"string","maxLength":100,"minLength":1,"description":"Placeholder text in the composer, e.g. 'Ask about an account…'. Defaults to the client's own placeholder when omitted."}}},"audio":{"type":"object","description":"Switchable audio for the assistant channel. The settings object keeps enablement beside its\n speech policy, matching the voice channel's switchable blocks so a disabled configuration can\n be retained without serving audio.","properties":{"enabled":{"type":"boolean","description":"Whether the assistant accepts audio calls. Required so the presence of saved speech configuration is never mistaken for enablement."},"speech":{"type":"object","description":"Recognition and synthesis policy for assistant audio. This is assistant-owned rather than a\n VoiceSpeechSettings reuse so telephony and assistant audio can evolve independently. It has no\n language greeting and no greet-every-language switch because an assistant conversation is\n already open when its audio call starts, leaving nothing for the channel to greet.","properties":{"languages":{"type":"array","items":{"type":"object","description":"One language the assistant audio channel can recognise and speak. There is deliberately no\n greeting here: an assistant conversation is already open when its audio call starts, so there\n is no new conversation to welcome and no initial phrase for the runtime to choose or repeat.","properties":{"code":{"type":"string","maxLength":8,"minLength":2,"pattern":"^[a-z]{2,3}(-[A-Z]{2})?$","description":"BCP-47 code with locale, e.g. 'en-US', 'it-IT'. Reuses the channel-neutral language value so assistant and voice configuration accept the same codes without sharing policy structures."},"voice":{"type":"string","maxLength":1024,"minLength":1,"pattern":"^[a-z0-9][a-z0-9_-]*://[a-zA-Z0-9._~/-]+(\\?[a-zA-Z0-9._~%&=+:/-]*)?$","description":"Voice this language speaks with. Reuses the channel-neutral URI value so providers and synthesis parameters have the same representation in every channel."},"isDefault":{"type":"boolean","description":"Marks the language used when the caller's language matches nothing configured.\n\n THE FULL RESOLUTION ORDER, because a client author reading only this contract cannot\n otherwise find it: an exact match on the call's language, then a match on its primary\n subtag (`fr` finds `fr-FR`), then the language marked here, then a language whose primary\n subtag is `en`, then the first entry in the list. The same order the voice channel resolves\n by — the rule belongs to choosing a language, not to a channel.\n\n At most one language may carry it. A record with two — reachable through the API, where no\n form prevents it — resolves to the first marked one in list order: refusing an\n already-open assistant conversation over configuration drift is worse than serving it."},"pronunciations":{"type":"array","items":{"type":"object","description":"A pronunciation rule for one phrase. Belongs to a LANGUAGE, not to the agent: 'Wildix' is\n respelled differently for an Italian and a German voice, so one list shared ACROSS languages\n would fight itself. That is why the rules hang off the language and nowhere else — there is no\n company-wide dictionary above them to inherit from, or to disagree with.","properties":{"phrase":{"type":"string","maxLength":200,"description":"The phrase as it appears in the text, e.g. 'Wildix'. Bounded because every rule is compiled into ONE regular expression — see the list below."},"as":{"type":"string","maxLength":200,"description":"Respelling (ALIAS) or phonetic transcription (IPA), e.g. 'Wil-dix'."},"encoding":{"type":"string","description":"How the `as` member of a pronunciation rule is written.","enum":["alias","ipa"]}},"required":["as","phrase"]},"maxItems":200,"description":"The rules this language is spoken with, same shape and same per-language ownership as\n VoiceLanguage.pronunciations — a respelling is about how a synthesized reply SOUNDS, not\n about how a conversation starts, so the rationale that leaves `greeting` off this structure\n does not apply here. Absent means no respellings for this language, distinct from the voice\n channel's OWN configured rules for the same language: an agent's assistant audio does not\n inherit them, so a name gets fixed once and read once, on whichever channel it is saved."}},"required":["code","voice"]},"maxItems":10,"minItems":1,"description":"Languages the assistant audio channel can recognise and speak. The @length bound is a\n STORAGE ceiling, not a promise about what a recogniser accepts at once — unlike the voice\n channel, where a caller opts into identification with `autoDetect`, THIS channel derives\n identification from `languages` and turns it on whenever the recogniser can identify among\n more than one, so every one of the up to ten rows here can become a live candidate list. The\n service does not clamp that list to a recogniser's own practical limit today (Amazon\n Transcribe's documented recommendation for streaming identification is five); a caller\n configuring more than a recogniser accepts should expect degraded identification or a\n rejected stream, not a refusal from this contract."},"transcription":{"type":"object","description":"Speech-to-text configuration owned by the assistant channel, so changes to assistant\n recognition policy cannot alter telephony calls.\n\n NO `autoDetect` HERE, unlike the voice channel: on this channel configuring a second language\n IS the request to recognise which one is being spoken — nobody lists two and then asks to be\n heard in only one — so the server derives identification from the language list and clamps it\n to false when the recogniser cannot identify among them, or there are too few to identify. The\n voice channel keeps the switch because a dialplan can hand a call its language, which is a\n choice an assistant conversation never has to make.","properties":{"engine":{"type":"string","maxLength":1024,"minLength":1,"pattern":"^[a-z0-9][a-z0-9_-]*://[a-zA-Z0-9._~/-]+(\\?[a-zA-Z0-9._~%&=+:/-]*)?$","description":"Recogniser used for assistant audio. Absent resolves to the platform default, resolved by the server so exactly one component decides it."}}}},"required":["languages"]}},"required":["enabled"]}}}}},"engine":{"description":"How the agent produces replies. `ai` is the managed engine; `webhook` and `sqs` delegate to an external backend.","oneOf":[{"type":"object","title":"ai","properties":{"ai":{"type":"object","description":"Managed AI engine: the platform runs the model, tools and knowledge lookups.","properties":{"instructions":{"type":"string","maxLength":60000,"minLength":1},"workflow":{"type":"object","description":"The agent's conversation as a graph: which step runs first, what each step does, and on what condition\n control moves on. Optional on AgentAiEngine — without one the agent is prompt-driven, steered by\n `instructions` and free to use its capabilities in any order.\n\n Versioned with the rest of the agent definition: publishing a version snapshots the graph, and a draft edit\n reaches no session until it is published and routed.","properties":{"entry":{"type":"string","maxLength":64,"minLength":1,"pattern":"^[a-zA-Z0-9_-]+$","description":"Step the graph starts at. Must be one of `steps`."},"steps":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","maxLength":64,"minLength":1,"pattern":"^[a-zA-Z0-9_-]+$","description":"Identifier of a step within its workflow, unique per workflow (the main graph and each nested workflow are separate scopes). Stable and machine-facing: transitions reference it, and a result step's id is the name a caller branches on, so renaming a step's `label` never unwires the graph."},"label":{"type":"string","maxLength":128,"minLength":1,"description":"Human readable name of a step, e.g. 'Transfer to the sales desk'. Shown in the builder and in traces, and\n read by the model too: when this step is an exit of the live step, the label is the preview offered alongside\n the exit's condition. Never a routing key — see AgentWorkflowStepId — so renaming one never unwires a graph."},"stage":{"type":"string","maxLength":64,"minLength":1,"description":"The phase this step belongs to. Inherited by steps downstream, so a handful of tags organise a long graph."},"handler":{"description":"What a step does. Exactly one member is set. `action` carries a capability; the others are workflow-only handlers with no capability equivalent.","oneOf":[{"type":"object","title":"conversation","properties":{"conversation":{"type":"object","description":"A stretch of conversation with a goal: the model talks, may use the capabilities in scope, and the step's\n transitions are evaluated against what happens. This is the only handler that leaves control where it is\n until a transition matches — a step whose conditions all stay false simply keeps talking.\n\n Deliberately not configurable per step: the model, and how readily the step is left. The model is the\n agent's (`engine.ai.model`) because a session that swaps models mid-conversation swaps voice, latency and\n tool-calling behaviour with it. How readily a step is left is decided by how its transitions are worded —\n conditions describing what the *caller* has done keep a model on the step, and no eagerness dial moves that\n number.","properties":{"goal":{"type":"string","maxLength":10000,"minLength":1,"description":"What the agent is trying to achieve here, appended to the agent's `engine.ai.instructions`. Never a replacement for them: identity, tone and standing rules hold across every step, and a step that needs different ones is a different agent."},"entry":{"type":"string","description":"How a conversation step starts.","enum":["auto","speak_first","listen_first"]},"channels":{"type":"object","description":"Per-channel settings of a conversation step. Only voice has anything to say about a stretch of conversation today; the chat and assistant surfaces deliver it as they deliver every other reply.","properties":{"voice":{"type":"object","description":"Voice-channel settings for a conversation step, overriding VoiceChannelSettings while it is live. Ignored on other channels.","properties":{"interruptions":{"type":"object"}}}}},"capabilities":{"type":"array","items":{"oneOf":[{"type":"object","title":"tool","properties":{"tool":{"type":"object","properties":{"id":{"type":"string","maxLength":12,"minLength":12,"pattern":"^[0-9A-Za-z]{12}$","description":"Unique identifier of the tool using nanoid format"},"toolVersion":{"type":"number","minimum":1,"description":"Pin a specific published tool version. When omitted, the tool's latestVersion is resolved at load, so a republished tool reaches this agent without a new agent version. Pin it when `fields` must keep aiming at an exact input schema."},"description":{"type":"string","maxLength":4096,"minLength":1,"description":"Override the tool's description for this agent. When omitted, the tool's own description is used."},"fields":{"type":"array","items":{"type":"object"},"description":"Per-field overrides on the tool input schema: pin fixed values (hidden from the model) or refine field descriptions. Fields not listed are filled by the model per the tool schema. Paths are resolved against the input schema of the tool version in effect — pin `toolVersion` when they must not move."},"startMessage":{"type":"string","description":"Message spoken when the tool call starts. Honoured on the voice channel. Spoken in every language of the call while `startMessages` is absent or empty; not consulted once `startMessages` carries any entry."},"startMessages":{"type":"array","items":{"type":"object"},"description":"Per-language start messages. When present and non-empty, the entry for the call's current\n language is spoken and `startMessage` is not consulted; a language with no entry is silent.\n When absent or empty, `startMessage` is spoken as before. On a call with no effective speech\n configuration — no configured languages, or a PBX below protocol 2 — `startMessage` is spoken\n regardless, because there is no current language to choose by."},"pipeline":{"type":"object"},"approval":{"type":"object"},"channels":{"type":"array","items":{"type":"object"},"uniqueItems":true,"description":"Channels this capability is offered on. Absent or empty — the default — means every channel the agent serves\n that can honour it. Listing channels narrows that set and never widens it: a `transfer` listed for `chat` is\n still ignored there, because the chat channel has no call to transfer."},"annotations":{"type":"array","items":{"type":"object"}}},"required":["id"]}},"required":["tool"]},{"type":"object","title":"connector","properties":{"connector":{"type":"object","description":"A configured connector (wilma-tools) exposed to the model as a single meta-tool named\n after the connector. The model calls the reserved `help` action to learn the callable\n actions, then dispatches an action through the same tool. Progressive disclosure runs\n through the connector tool itself, not the skills catalogue.","properties":{"id":{"type":"string","maxLength":12,"minLength":12,"pattern":"^[0-9A-Za-z]{12}$","description":"Configured connector instance in wilma-tools. Its pinned definition, permission `grant` and authorization govern what the model can discover via `help` and call. There is no version pin here, unlike `AgentTool.toolVersion`: the action catalogue and its schemas are already frozen by the connector's pinned definition, while `grant` and authorization are read live so permission changes apply at once."},"startMessage":{"type":"string","description":"Message spoken when a connector action call starts. Honoured on the voice channel. Mirrors `AgentTool.startMessage`. Not applied to the read-only `help` action. Not consulted once `startMessages` carries any entry."},"startMessages":{"type":"array","items":{"type":"object"},"description":"Forward-compatible per-language connector start messages, mirroring `AgentTool.startMessages`. Accepted and stored so clients can configure connector and tool phrases consistently, but currently ignored at execution time because the voice runtime does not read connector start messages yet. Runtime enablement is tracked in wilma-services docs/todo.md item 8."},"pipeline":{"type":"object"},"approval":{"type":"object"},"channels":{"type":"array","items":{"type":"object"},"uniqueItems":true,"description":"Channels this capability is offered on. Absent or empty — the default — means every channel the agent serves\n that can honour it. Listing channels narrows that set and never widens it: a `transfer` listed for `chat` is\n still ignored there, because the chat channel has no call to transfer."},"annotations":{"type":"array","items":{"type":"object"}}},"required":["id"]}},"required":["connector"]},{"type":"object","title":"kb","properties":{"kb":{"type":"object","properties":{"knowledgeBaseId":{"type":"string"},"instructions":{"type":"string","maxLength":60000,"minLength":1,"description":"Additional instructions for when the agent should use this knowledge base."},"searchStrategy":{"type":"object"},"searchThreshold":{"type":"object"},"searchLimit":{"type":"number","maximum":50,"minimum":1,"description":"Maximum number of passages this capability returns. Defaults to the knowledge base service's own default (10) when omitted. Keep it low on voice: every passage is prompt the model reads before it can speak."},"channels":{"type":"array","items":{"type":"object"},"uniqueItems":true,"description":"Channels this capability is offered on. Absent or empty — the default — means every channel the agent serves\n that can honour it. Listing channels narrows that set and never widens it: a `transfer` listed for `chat` is\n still ignored there, because the chat channel has no call to transfer."},"annotations":{"type":"array","items":{"type":"object"}}}}},"required":["kb"]},{"type":"object","title":"sandbox","properties":{"sandbox":{"description":"Embedded sandbox: shell + file tools backed by an execution environment. The only capability with no `channels` list — being a union it has nowhere to carry one — so it is offered on every channel the agent serves.","oneOf":[{"type":"object","title":"inline","properties":{"inline":{"type":"object"}},"required":["inline"]},{"type":"object","title":"snapshot","properties":{"snapshot":{"type":"object"}},"required":["snapshot"]}]}},"required":["sandbox"]},{"type":"object","title":"webSearch","properties":{"webSearch":{"type":"object","description":"Model-native web search. The provider tool is selected from the agent model.","properties":{"contextSize":{"type":"object"},"allowedDomains":{"type":"array","items":{"type":"string","maxLength":253,"minLength":1},"description":"Restrict results to these domains where the provider supports it."},"userLocation":{"type":"object"},"channels":{"type":"array","items":{"type":"object"},"uniqueItems":true,"description":"Channels this capability is offered on. Absent or empty — the default — means every channel the agent serves\n that can honour it. Listing channels narrows that set and never widens it: a `transfer` listed for `chat` is\n still ignored there, because the chat channel has no call to transfer."}}}},"required":["webSearch"]},{"type":"object","title":"webFetch","properties":{"webFetch":{"type":"object","description":"Server-side fetch of a URL, returning its readable text/markdown to the model.","properties":{"allowedDomains":{"type":"array","items":{"type":"string","maxLength":253,"minLength":1},"description":"Restrict fetches to these domains. Empty list means any domain is allowed."},"channels":{"type":"array","items":{"type":"object"},"uniqueItems":true,"description":"Channels this capability is offered on. Absent or empty — the default — means every channel the agent serves\n that can honour it. Listing channels narrows that set and never widens it: a `transfer` listed for `chat` is\n still ignored there, because the chat channel has no call to transfer."}}}},"required":["webFetch"]},{"type":"object","title":"agent","properties":{"agent":{"type":"object","description":"Invoke another agent as a dedicated execution with its own state. The called agent runs independently and returns its result.","properties":{"agentId":{"type":"string","maxLength":12,"minLength":12},"agentVersion":{"type":"number","minimum":1,"description":"Pin a specific published version of the called agent. When absent, the called agent's deployment configuration selects the version at invocation time."},"access":{"type":"object"},"variables":{"type":"array","items":{"type":"object"}},"channels":{"type":"array","items":{"type":"object"},"uniqueItems":true,"description":"Channels this capability is offered on. Absent or empty — the default — means every channel the agent serves\n that can honour it. Listing channels narrows that set and never widens it: a `transfer` listed for `chat` is\n still ignored there, because the chat channel has no call to transfer."}},"required":["agentId"]}},"required":["agent"]},{"type":"object","title":"handover","properties":{"handover":{"type":"object","description":"Hand the conversation over to a human and leave it. Honoured on conversation channels (chat, assistant).","properties":{"name":{"type":"string","description":"Tool name exposed to the model. Auto-generated when omitted."},"description":{"type":"string","description":"Description for the model to decide when to hand over."},"variant":{"type":"object"},"agent":{"type":"string","maxLength":12,"minLength":12,"description":"Wilma agent to hand over to when variant = AGENT."},"agentVersion":{"type":"number","minimum":1,"description":"Pin a specific published version of the target agent when variant = AGENT. When absent, the target's deployment configuration selects the version at handover time."},"service":{"type":"string","description":"x-bees service id to hand over to when variant = SERVICE."},"silent":{"type":"boolean","description":"Hand over without posting a visible message to the user."},"pipeline":{"type":"object"},"channels":{"type":"array","items":{"type":"object"},"uniqueItems":true,"description":"Channels this capability is offered on. Absent or empty — the default — means every channel the agent serves\n that can honour it. Listing channels narrows that set and never widens it: a `transfer` listed for `chat` is\n still ignored there, because the chat channel has no call to transfer."},"annotations":{"type":"array","items":{"type":"object"}}}}},"required":["handover"]},{"type":"object","title":"delegation","properties":{"delegation":{"type":"object","description":"Delegate the conversation to another agent (max 3 transitions per turn). One capability entry per delegate target.","properties":{"name":{"type":"string","description":"Tool name exposed to the model. Auto-generated when omitted."},"description":{"type":"string","description":"Description for the model to decide when to delegate."},"id":{"type":"string","maxLength":12,"minLength":12,"description":"Agent to delegate to."},"agentVersion":{"type":"number","minimum":1,"description":"Pin a specific published version of the delegate agent. When absent, the delegate's deployment configuration selects the version at delegation time."},"channels":{"type":"array","items":{"type":"object"},"uniqueItems":true,"description":"Channels this capability is offered on. Absent or empty — the default — means every channel the agent serves\n that can honour it. Listing channels narrows that set and never widens it: a `transfer` listed for `chat` is\n still ignored there, because the chat channel has no call to transfer."},"annotations":{"type":"array","items":{"type":"object"}}},"required":["id"]}},"required":["delegation"]},{"type":"object","title":"transfer","properties":{"transfer":{"type":"object","description":"Transfer the call to a PBX destination. Honoured on the voice channel. One capability entry per transfer target.","properties":{"name":{"type":"string","description":"Tool name exposed to the model. Auto-generated when omitted."},"description":{"type":"string","description":"Description for the model to decide when to transfer."},"variant":{"type":"object"},"context":{"type":"string","description":"Routing context for PREDEFINED transfers."},"extension":{"type":"string","description":"Extension or phone number for PREDEFINED transfers."},"pipeline":{"type":"object"},"channels":{"type":"array","items":{"type":"object"},"uniqueItems":true,"description":"Channels this capability is offered on. Absent or empty — the default — means every channel the agent serves\n that can honour it. Listing channels narrows that set and never widens it: a `transfer` listed for `chat` is\n still ignored there, because the chat channel has no call to transfer."},"annotations":{"type":"array","items":{"type":"object"}}}}},"required":["transfer"]},{"type":"object","title":"suggestions","properties":{"suggestions":{"type":"object","description":"Emit suggested replies for human operators. Honoured on the chat channel.","properties":{"name":{"type":"string","description":"Tool name. Auto-generated when omitted."},"instructions":{"type":"string","maxLength":2000,"minLength":1,"description":"Steers what the suggestions say. Appended to the platform's own generation prompt rather than\n replacing it, so the shape of a suggestion — short, tappable, a plausible next thing to send —\n stays the platform's to keep, and this narrows the subject: 'offer a callback and a link to the\n status page, never a discount'.\n\n NOT a tool description, unlike the `description` every other capability carries: suggestions are\n not a tool the model chooses to call. They are generated from a reply that has already been\n written, so there is no decision to describe — only the generation to steer.\n\n Absent means the platform's prompt alone."},"channels":{"type":"array","items":{"type":"object"},"uniqueItems":true,"description":"Channels this capability is offered on. Absent or empty — the default — means every channel the agent serves\n that can honour it. Listing channels narrows that set and never widens it: a `transfer` listed for `chat` is\n still ignored there, because the chat channel has no call to transfer."},"annotations":{"type":"array","items":{"type":"object"}}}}},"required":["suggestions"]},{"type":"object","title":"hangup","properties":{"hangup":{"type":"object","description":"Let the model end the call. Honoured on the voice channel.","properties":{"name":{"type":"string","description":"Tool name exposed to the model. Auto-generated when omitted."},"description":{"type":"string","description":"Description for the model to decide when to hang up."},"pipeline":{"type":"object"},"channels":{"type":"array","items":{"type":"object"},"uniqueItems":true,"description":"Channels this capability is offered on. Absent or empty — the default — means every channel the agent serves\n that can honour it. Listing channels narrows that set and never widens it: a `transfer` listed for `chat` is\n still ignored there, because the chat channel has no call to transfer."},"annotations":{"type":"array","items":{"type":"object"}}}}},"required":["hangup"]},{"type":"object","title":"wait","properties":{"wait":{"type":"object","description":"Let the model hold the call while it works (e.g. a lookup), keeping the caller on the line. Honoured on the voice channel.","properties":{"name":{"type":"string","description":"Tool name exposed to the model. Auto-generated when omitted."},"description":{"type":"string","description":"Description for the model to decide when to wait."},"message":{"type":"string","description":"Message spoken when entering the wait state. Spoken in every language of the call while `messages` is absent or empty; not consulted once `messages` carries any entry."},"messages":{"type":"array","items":{"type":"object"},"description":"Per-language wait messages. Same rule as `AgentTool.startMessages`: the entry for the call's current language is spoken, a language with no entry is silent, and with no effective speech configuration `message` is spoken."},"channels":{"type":"array","items":{"type":"object"},"uniqueItems":true,"description":"Channels this capability is offered on. Absent or empty — the default — means every channel the agent serves\n that can honour it. Listing channels narrows that set and never widens it: a `transfer` listed for `chat` is\n still ignored there, because the chat channel has no call to transfer."},"annotations":{"type":"array","items":{"type":"object"}}}}},"required":["wait"]}]},"description":"Capabilities offered to the model *in addition* to `engine.ai.capabilities` while this step runs,\n configured exactly as they are there — the same union, the same structures. Use it to give a step reach\n it should not have everywhere: authoring the refund tool on the refund step, rather than on the agent, is\n also what keeps it out of every other step.\n\n Additive only, by design. A step cannot drop an agent-level capability, so it cannot strand a session by\n taking away the `handover` or `transfer` its channel needs, and the runtime keeps one rule for the turn's\n tool set. Skills the model loads (`engine.ai.skills`) and the channel's own built-ins join it either way."}},"required":["goal"]}},"required":["conversation"]},{"type":"object","title":"message","properties":{"message":{"type":"object","description":"Say one thing — spoken on voice, posted on chat / assistant — then move on. The line is either authored verbatim or generated at the step; either way the agent says it once and the transitions are evaluated, with no turn handed to the user.","properties":{"content":{"description":"Where the step's line comes from. Exactly one member is set: `text` is authored, `generate` is written by the model at the step.","oneOf":[{"type":"object","title":"text","properties":{"text":{"type":"string","maxLength":4000,"minLength":1,"description":"The line, verbatim. No model in the loop, which is what makes it the right content for a disclosure, a policy sentence or a legally worded confirmation."}},"required":["text"]},{"type":"object","title":"generate","properties":{"generate":{"type":"object","description":"A line the model writes at this step, from the conversation and whatever the graph has gathered — 'greet the\n caller by the name on the contact record and mention any open orders'. One model call, no tools offered, no\n waiting for a reply: the agent says exactly one thing and the step's transitions are evaluated.\n\n The difference from a conversation step is that this is a *speech act*, not a stretch of conversation: the\n model cannot decide to stay quiet, cannot act, and cannot keep the step. Use it where the words must fit the\n data but the shape of the session must not depend on how they came out.","properties":{"instructions":{"type":"string","maxLength":4000,"minLength":1,"description":"What to say, as an instruction to the model rather than the words themselves. Supports handlebars resolved against workflow variables, agent variables and session context, so an action's `output` captured a step earlier is available verbatim (e.g. 'Greet {{contact.firstName}} and mention their {{contact.openOrders}} open orders')."}},"required":["instructions"]}},"required":["generate"]}]},"channels":{"type":"object","description":"Per-channel settings of a message step: how the line is protected when spoken, and what is offered with it when posted.","properties":{"voice":{"type":"object","description":"Voice-channel settings for a message step, honoured while the line is spoken. Ignored on other channels.","properties":{"interruptions":{"type":"object"}}},"chat":{"type":"object","description":"Chat-channel settings for a message step, honoured where a line is posted rather than spoken. Ignored on voice.","properties":{"suggestions":{"type":"array","items":{"type":"object"},"maxItems":10,"minItems":1,"description":"Reply buttons posted with the line, e.g. 'Yes, go ahead' / 'No, not now' under a confirmation. Delivered\n as an x-bees `actions` element of `button` elements, so a tap either sends a reply — which the step's\n transitions then read like any other user message — or opens a link. Only offered on a fixed line: a\n conversation step is the model's to word, and buttons authored under words it has not chosen yet would\n contradict each other."}}}}}},"required":["content"]}},"required":["message"]},{"type":"object","title":"action","properties":{"action":{"type":"object","description":"A step that performs one capability. The runtime performs it on arrival — no model decision about *whether*\n to act, and no tool offered to the model for it. Which is the point: an action the graph reached is an action\n that happens, at the cost of nothing.","properties":{"capability":{"description":"The capability a step performs, run by the runtime rather than chosen by the model. A subset of\n AgentCapability, reusing its structures verbatim: these are the members that describe a single action with a\n beginning and an end. The rest of AgentCapability (`kb`, `webSearch`, `webFetch`, `sandbox`, `suggestions`)\n describes reach the model draws on while it talks, so it belongs on a conversation step's `capabilities`\n rather than on a step of its own.\n\n Configuration comes from the capability itself and nothing is added here: a tool step's fixed arguments are\n `AgentTool.fields` predefined handlers, its spoken lead-in is `AgentTool.startMessage`, a transfer's line\n before the hand-off is `AgentTransferPipeline.instructions`, an invoked agent's inputs are\n `AgentCallCapability.variables`. Anything left unpinned is filled by the model at the step, per the tool's\n input schema.","oneOf":[{"type":"object","title":"tool","properties":{"tool":{"type":"object","properties":{"id":{"type":"string","maxLength":12,"minLength":12,"pattern":"^[0-9A-Za-z]{12}$","description":"Unique identifier of the tool using nanoid format"},"toolVersion":{"type":"number","minimum":1,"description":"Pin a specific published tool version. When omitted, the tool's latestVersion is resolved at load, so a republished tool reaches this agent without a new agent version. Pin it when `fields` must keep aiming at an exact input schema."},"description":{"type":"string","maxLength":4096,"minLength":1,"description":"Override the tool's description for this agent. When omitted, the tool's own description is used."},"fields":{"type":"array","items":{"type":"object"},"description":"Per-field overrides on the tool input schema: pin fixed values (hidden from the model) or refine field descriptions. Fields not listed are filled by the model per the tool schema. Paths are resolved against the input schema of the tool version in effect — pin `toolVersion` when they must not move."},"startMessage":{"type":"string","description":"Message spoken when the tool call starts. Honoured on the voice channel. Spoken in every language of the call while `startMessages` is absent or empty; not consulted once `startMessages` carries any entry."},"startMessages":{"type":"array","items":{"type":"object"},"description":"Per-language start messages. When present and non-empty, the entry for the call's current\n language is spoken and `startMessage` is not consulted; a language with no entry is silent.\n When absent or empty, `startMessage` is spoken as before. On a call with no effective speech\n configuration — no configured languages, or a PBX below protocol 2 — `startMessage` is spoken\n regardless, because there is no current language to choose by."},"pipeline":{"type":"object"},"approval":{"type":"object"},"channels":{"type":"array","items":{"type":"object"},"uniqueItems":true,"description":"Channels this capability is offered on. Absent or empty — the default — means every channel the agent serves\n that can honour it. Listing channels narrows that set and never widens it: a `transfer` listed for `chat` is\n still ignored there, because the chat channel has no call to transfer."},"annotations":{"type":"array","items":{"type":"object"}}},"required":["id"]}},"required":["tool"]},{"type":"object","title":"connector","properties":{"connector":{"type":"object","description":"A configured connector (wilma-tools) exposed to the model as a single meta-tool named\n after the connector. The model calls the reserved `help` action to learn the callable\n actions, then dispatches an action through the same tool. Progressive disclosure runs\n through the connector tool itself, not the skills catalogue.","properties":{"id":{"type":"string","maxLength":12,"minLength":12,"pattern":"^[0-9A-Za-z]{12}$","description":"Configured connector instance in wilma-tools. Its pinned definition, permission `grant` and authorization govern what the model can discover via `help` and call. There is no version pin here, unlike `AgentTool.toolVersion`: the action catalogue and its schemas are already frozen by the connector's pinned definition, while `grant` and authorization are read live so permission changes apply at once."},"startMessage":{"type":"string","description":"Message spoken when a connector action call starts. Honoured on the voice channel. Mirrors `AgentTool.startMessage`. Not applied to the read-only `help` action. Not consulted once `startMessages` carries any entry."},"startMessages":{"type":"array","items":{"type":"object"},"description":"Forward-compatible per-language connector start messages, mirroring `AgentTool.startMessages`. Accepted and stored so clients can configure connector and tool phrases consistently, but currently ignored at execution time because the voice runtime does not read connector start messages yet. Runtime enablement is tracked in wilma-services docs/todo.md item 8."},"pipeline":{"type":"object"},"approval":{"type":"object"},"channels":{"type":"array","items":{"type":"object"},"uniqueItems":true,"description":"Channels this capability is offered on. Absent or empty — the default — means every channel the agent serves\n that can honour it. Listing channels narrows that set and never widens it: a `transfer` listed for `chat` is\n still ignored there, because the chat channel has no call to transfer."},"annotations":{"type":"array","items":{"type":"object"}}},"required":["id"]}},"required":["connector"]},{"type":"object","title":"agent","properties":{"agent":{"type":"object","description":"Invoke another agent as a dedicated execution with its own state. The called agent runs independently and returns its result.","properties":{"agentId":{"type":"string","maxLength":12,"minLength":12},"agentVersion":{"type":"number","minimum":1,"description":"Pin a specific published version of the called agent. When absent, the called agent's deployment configuration selects the version at invocation time."},"access":{"type":"object"},"variables":{"type":"array","items":{"type":"object"}},"channels":{"type":"array","items":{"type":"object"},"uniqueItems":true,"description":"Channels this capability is offered on. Absent or empty — the default — means every channel the agent serves\n that can honour it. Listing channels narrows that set and never widens it: a `transfer` listed for `chat` is\n still ignored there, because the chat channel has no call to transfer."}},"required":["agentId"]}},"required":["agent"]},{"type":"object","title":"handover","properties":{"handover":{"type":"object","description":"Hand the conversation over to a human and leave it. Honoured on conversation channels (chat, assistant).","properties":{"name":{"type":"string","description":"Tool name exposed to the model. Auto-generated when omitted."},"description":{"type":"string","description":"Description for the model to decide when to hand over."},"variant":{"type":"object"},"agent":{"type":"string","maxLength":12,"minLength":12,"description":"Wilma agent to hand over to when variant = AGENT."},"agentVersion":{"type":"number","minimum":1,"description":"Pin a specific published version of the target agent when variant = AGENT. When absent, the target's deployment configuration selects the version at handover time."},"service":{"type":"string","description":"x-bees service id to hand over to when variant = SERVICE."},"silent":{"type":"boolean","description":"Hand over without posting a visible message to the user."},"pipeline":{"type":"object"},"channels":{"type":"array","items":{"type":"object"},"uniqueItems":true,"description":"Channels this capability is offered on. Absent or empty — the default — means every channel the agent serves\n that can honour it. Listing channels narrows that set and never widens it: a `transfer` listed for `chat` is\n still ignored there, because the chat channel has no call to transfer."},"annotations":{"type":"array","items":{"type":"object"}}}}},"required":["handover"]},{"type":"object","title":"delegation","properties":{"delegation":{"type":"object","description":"Delegate the conversation to another agent (max 3 transitions per turn). One capability entry per delegate target.","properties":{"name":{"type":"string","description":"Tool name exposed to the model. Auto-generated when omitted."},"description":{"type":"string","description":"Description for the model to decide when to delegate."},"id":{"type":"string","maxLength":12,"minLength":12,"description":"Agent to delegate to."},"agentVersion":{"type":"number","minimum":1,"description":"Pin a specific published version of the delegate agent. When absent, the delegate's deployment configuration selects the version at delegation time."},"channels":{"type":"array","items":{"type":"object"},"uniqueItems":true,"description":"Channels this capability is offered on. Absent or empty — the default — means every channel the agent serves\n that can honour it. Listing channels narrows that set and never widens it: a `transfer` listed for `chat` is\n still ignored there, because the chat channel has no call to transfer."},"annotations":{"type":"array","items":{"type":"object"}}},"required":["id"]}},"required":["delegation"]},{"type":"object","title":"transfer","properties":{"transfer":{"type":"object","description":"Transfer the call to a PBX destination. Honoured on the voice channel. One capability entry per transfer target.","properties":{"name":{"type":"string","description":"Tool name exposed to the model. Auto-generated when omitted."},"description":{"type":"string","description":"Description for the model to decide when to transfer."},"variant":{"type":"object"},"context":{"type":"string","description":"Routing context for PREDEFINED transfers."},"extension":{"type":"string","description":"Extension or phone number for PREDEFINED transfers."},"pipeline":{"type":"object"},"channels":{"type":"array","items":{"type":"object"},"uniqueItems":true,"description":"Channels this capability is offered on. Absent or empty — the default — means every channel the agent serves\n that can honour it. Listing channels narrows that set and never widens it: a `transfer` listed for `chat` is\n still ignored there, because the chat channel has no call to transfer."},"annotations":{"type":"array","items":{"type":"object"}}}}},"required":["transfer"]},{"type":"object","title":"hangup","properties":{"hangup":{"type":"object","description":"Let the model end the call. Honoured on the voice channel.","properties":{"name":{"type":"string","description":"Tool name exposed to the model. Auto-generated when omitted."},"description":{"type":"string","description":"Description for the model to decide when to hang up."},"pipeline":{"type":"object"},"channels":{"type":"array","items":{"type":"object"},"uniqueItems":true,"description":"Channels this capability is offered on. Absent or empty — the default — means every channel the agent serves\n that can honour it. Listing channels narrows that set and never widens it: a `transfer` listed for `chat` is\n still ignored there, because the chat channel has no call to transfer."},"annotations":{"type":"array","items":{"type":"object"}}}}},"required":["hangup"]},{"type":"object","title":"wait","properties":{"wait":{"type":"object","description":"Let the model hold the call while it works (e.g. a lookup), keeping the caller on the line. Honoured on the voice channel.","properties":{"name":{"type":"string","description":"Tool name exposed to the model. Auto-generated when omitted."},"description":{"type":"string","description":"Description for the model to decide when to wait."},"message":{"type":"string","description":"Message spoken when entering the wait state. Spoken in every language of the call while `messages` is absent or empty; not consulted once `messages` carries any entry."},"messages":{"type":"array","items":{"type":"object"},"description":"Per-language wait messages. Same rule as `AgentTool.startMessages`: the entry for the call's current language is spoken, a language with no entry is silent, and with no effective speech configuration `message` is spoken."},"channels":{"type":"array","items":{"type":"object"},"uniqueItems":true,"description":"Channels this capability is offered on. Absent or empty — the default — means every channel the agent serves\n that can honour it. Listing channels narrows that set and never widens it: a `transfer` listed for `chat` is\n still ignored there, because the chat channel has no call to transfer."},"annotations":{"type":"array","items":{"type":"object"}}}}},"required":["wait"]}]},"forceToolCall":{"type":"boolean","description":"Run the step as one model call with the tool choice pinned to this capability, instead of calling it\n straight from the runtime. The model still cannot decide *whether* to call it — only what to put in the\n arguments left unpinned, which is what a lookup keyed on something nobody can template ('the symptom the\n caller just described') needs.\n\n Costs one model call where the default costs none, so it is opt-in. Honoured where the action has\n arguments a model can write — `tool`, `connector`, `agent`, and a `transfer` whose target the model\n chooses; ignored elsewhere. Leaving it false while a required argument is unpinned is an authoring error,\n not a runtime one: nothing would be there to fill it."},"output":{"type":"string","maxLength":128,"minLength":1,"pattern":"^[a-zA-Z0-9_]+$","description":"Workflow variable the action's result is written to, so later `expression` conditions can branch on it and\n later steps can template it (e.g. capture a lookup into `policy`, then branch on\n `{ \"==\": [{ \"var\": \"policy.status\" }, \"active\"] }`). Absent means the result is not captured; the\n model still sees it in the conversation per the capability's own pipeline.\n\n Written on success only. A failed action leaves the variable as it was and takes a `failure` transition\n instead — see AgentWorkflowFailureCondition — so a graph can never mistake an error for a result."}},"required":["capability"]}},"required":["action"]},{"type":"object","title":"variables","properties":{"variables":{"type":"object","description":"Write workflow variables. The step nobody asks for until the first time a graph has to remember something between two branches.","properties":{"assignments":{"type":"array","items":{"type":"object","description":"One `name = value` write. Exactly one of `value` or `expression` is set: `value` renders text, `expression` computes.","properties":{"name":{"type":"string","maxLength":128,"minLength":1,"pattern":"^[a-zA-Z0-9_]+$"},"value":{"type":"string","maxLength":4000,"description":"Text written to the variable, with handlebars resolved against workflow variables, agent variables and session context (e.g. '{{lookup.status}}'). Empty clears the variable."},"expression":{"description":"A JsonLogic expression computing the value instead of rendering it, which is what makes arithmetic possible: `{ \"+\": [{ \"var\": \"attempts\" }, 1] }` is the retry counter a bounded loop needs, written with no model in the loop."}},"required":["name"]},"maxItems":50,"minItems":1}},"required":["assignments"]}},"required":["variables"]},{"type":"object","title":"workflow","properties":{"workflow":{"type":"object","description":"Run a nested workflow and continue once it returns.\n\n The unit of decomposition: a twelve-step identity check is called from the front door, from the booking flow\n and from the complaints line, so it is authored once and referenced by a single step at each call site. The\n nested workflow shares this session and its variables; it returns through its `result` steps, which is what\n the calling step's `result` transitions branch on. A nested workflow that instead reaches an `end` or a\n terminal action does what it says — the session is over and nothing comes back.","properties":{"workflow":{"type":"string","maxLength":64,"minLength":1,"pattern":"^[a-zA-Z0-9_-]+$","description":"The nested workflow to run. Must be declared in `AgentWorkflow.nested`, and must not run itself directly or through a ring of nested workflows."},"output":{"type":"string","maxLength":128,"minLength":1,"pattern":"^[a-zA-Z0-9_]+$","description":"Workflow variable the nested workflow's result `value` is written to. Absent means the value is discarded — which is the usual case when the branch taken is the whole answer."}},"required":["workflow"]}},"required":["workflow"]},{"type":"object","title":"result","properties":{"result":{"type":"object","description":"End this workflow and hand control back to whatever invoked it.\n\n In a nested workflow the invoking step resumes and branches on which result was reached (see\n AgentWorkflowResultCondition). In the main graph of a workflow-channel agent the run finishes and the result\n is returned to the caller — the agent, automation or API request that started it. In the main graph of a\n voice / chat / assistant agent there is no invoker, so it behaves as `end`.","properties":{"value":{"type":"string","maxLength":8000,"minLength":1,"description":"What this result returns to the invoker. Supports handlebars resolved against workflow variables, agent variables and session context (e.g. '{{policy.number}}'). Absent returns the result's id alone, which is enough when the caller only branches on which result was reached."}}}},"required":["result"]},{"type":"object","title":"end","properties":{"end":{"type":"object","description":"The session is over. Terminal and configuration-free by design: a closing line is a `message` step in front of it, and releasing a call is the `hangup` action — both already exist, and neither needs restating here."}},"required":["end"]}]},"transitions":{"type":"array","items":{"type":"object","description":"One way out of a step.","properties":{"target":{"type":"string","maxLength":64,"minLength":1,"pattern":"^[a-zA-Z0-9_-]+$","description":"Step to move to. Must be a step of the same workflow — a nested workflow is entered through a `workflow` step, not by pointing a transition into it."},"condition":{"description":"How the runtime decides to take a transition. Exactly one member is set.","oneOf":[{"type":"object","title":"always","properties":{"always":{"type":"object","description":"Unconditional. Only legal as a step's single transition: two unconditional exits are a coin toss, not a routing decision."}},"required":["always"]},{"type":"object","title":"prompt","properties":{"prompt":{"type":"object","description":"A condition the model judges against the conversation, e.g. 'the caller has said they will try the fix'.\n\n Phrase it as something the **caller** has done. A condition describing what the *agent* has done — 'the\n caller has a next step to try' — is true the instant the agent names one, so the model leaves before the\n caller has answered, and one early exit cascades through the rest of the graph. Rewording the same exits from\n agent actions to caller actions, changing nothing else, moved three models from 58–64 % of turns on the right\n step to 96–100 %, and removed every stray move. It is the single largest effect measured on this contract, so\n an authoring surface that accepts an agent-action condition is the defect, not the wording.","properties":{"instructions":{"type":"string","maxLength":2000,"minLength":1}},"required":["instructions"]}},"required":["prompt"]},{"type":"object","title":"expression","properties":{"expression":{"type":"object","description":"A branch taken when workflow state satisfies the condition — no model judgement involved, which is what makes it the right condition after an `action` or `variables` step.","properties":{"conditions":{"description":"A JsonLogic condition tree evaluated against workflow state, e.g.\n `{ \"==\": [ { \"var\": \"lookup.status\" }, \"found\" ] }`. The `var` namespace is the workflow variables\n written by `variables` steps and action `output` captures, the agent's input variables\n (`engine.ai.variables`), and the session context. An empty tree always matches. This is a\n separate language from the rule-tree AutomationConditions (wilma-workflows) and CopilotConditions\n (wilma-copilot)."}},"required":["conditions"]}},"required":["expression"]},{"type":"object","title":"result","properties":{"result":{"type":"object","description":"A branch on which result a nested workflow returned. Only meaningful on a `workflow` step; the referenced step must be a `result` step of the nested workflow that step runs.","properties":{"result":{"type":"string","maxLength":64,"minLength":1,"pattern":"^[a-zA-Z0-9_-]+$","description":"Id of the `result` step reached inside the nested workflow. An id rather than a name, so renaming a result from 'failed' to 'gave up' does not unwire every step that handles it."}},"required":["result"]}},"required":["result"]},{"type":"object","title":"failure","properties":{"failure":{"type":"object","description":"A branch taken when the step's action fails. Only meaningful on an `action` step, and the only condition a\n terminal action may carry: a transfer that finds the extension busy has not left the session, so a graph that\n cannot say what happens next simply drops the caller.\n\n Failure short-circuits — when an action fails, only `failure` transitions are considered, never the\n `expression` or `prompt` branches that were written for its result. If none matches, the session fails with\n the action's error, so a step whose capability can fail wants a catch-all last in the list.","properties":{"kinds":{"type":"array","items":{"type":"string","description":"How an action failed, as far as the runtime can tell. Deliberately a short list of kinds an author can write\n a different recovery for — a busy extension is a different flow from an expired credential — rather than a\n mirror of every provider's error codes. The message and the provider's own detail stay on the invocation\n event and the trace; a graph branches on the kind.","enum":["timeout","denied","unauthorized","not_found","invalid_input","rate_limited","unavailable","error"]},"description":"Kinds this branch handles, evaluated as a set. Absent or empty catches **any** failure — the catch-all, which the list order must therefore place after the specific branches."}}}},"required":["failure"]},{"type":"object","title":"fallback","properties":{"fallback":{"type":"object","description":"The else-branch, taken when no sibling matched. At most one per step, and reached last regardless of where it sits in the list."}},"required":["fallback"]}]}},"required":["condition","target"]},"description":"The ways out of this step. Must be non-empty on every step that stays in the session, since a step the\n runtime cannot leave is a dead end.\n\n On `result` and `end` it must be empty. On the `action` members that leave the session (`handover`,\n `delegation`, `transfer`, `hangup`) it may carry `failure` transitions and nothing else: an action that\n was supposed to leave and failed — a busy extension, a service desk with nobody on it — has not left, and\n a graph with no answer for that drops the caller."},"position":{"type":"object","description":"Canvas coordinates of a step in the builder. Authoring metadata: the runtime ignores it, and a workflow that arrives without positions is laid out automatically.","properties":{"x":{"type":"number"},"y":{"type":"number"}},"required":["x","y"]}},"required":["handler","id","label"]},"maxItems":200,"minItems":1,"description":"Steps of one graph. Ids are unique within it."},"nested":{"type":"array","items":{"type":"object","description":"A named graph the main workflow — or another nested workflow — can run, referenced by AgentWorkflowInvokeHandler.","properties":{"entry":{"type":"string","maxLength":64,"minLength":1,"pattern":"^[a-zA-Z0-9_-]+$","description":"Step the graph starts at. Must be one of `steps`."},"steps":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","maxLength":64,"minLength":1,"pattern":"^[a-zA-Z0-9_-]+$","description":"Identifier of a step within its workflow, unique per workflow (the main graph and each nested workflow are separate scopes). Stable and machine-facing: transitions reference it, and a result step's id is the name a caller branches on, so renaming a step's `label` never unwires the graph."},"label":{"type":"string","maxLength":128,"minLength":1,"description":"Human readable name of a step, e.g. 'Transfer to the sales desk'. Shown in the builder and in traces, and\n read by the model too: when this step is an exit of the live step, the label is the preview offered alongside\n the exit's condition. Never a routing key — see AgentWorkflowStepId — so renaming one never unwires a graph."},"stage":{"type":"string","maxLength":64,"minLength":1,"description":"The phase this step belongs to. Inherited by steps downstream, so a handful of tags organise a long graph."},"handler":{"description":"What a step does. Exactly one member is set. `action` carries a capability; the others are workflow-only handlers with no capability equivalent.","oneOf":[{"type":"object","title":"conversation","properties":{"conversation":{"type":"object","description":"A stretch of conversation with a goal: the model talks, may use the capabilities in scope, and the step's\n transitions are evaluated against what happens. This is the only handler that leaves control where it is\n until a transition matches — a step whose conditions all stay false simply keeps talking.\n\n Deliberately not configurable per step: the model, and how readily the step is left. The model is the\n agent's (`engine.ai.model`) because a session that swaps models mid-conversation swaps voice, latency and\n tool-calling behaviour with it. How readily a step is left is decided by how its transitions are worded —\n conditions describing what the *caller* has done keep a model on the step, and no eagerness dial moves that\n number.","properties":{"goal":{"type":"string","maxLength":10000,"minLength":1,"description":"What the agent is trying to achieve here, appended to the agent's `engine.ai.instructions`. Never a replacement for them: identity, tone and standing rules hold across every step, and a step that needs different ones is a different agent."},"entry":{"type":"string","description":"How a conversation step starts.","enum":["auto","speak_first","listen_first"]},"channels":{"type":"object","description":"Per-channel settings of a conversation step. Only voice has anything to say about a stretch of conversation today; the chat and assistant surfaces deliver it as they deliver every other reply.","properties":{"voice":{"type":"object"}}},"capabilities":{"type":"array","items":{"oneOf":[{"type":"object","title":"tool","properties":{"tool":{"type":"object"}},"required":["tool"]},{"type":"object","title":"connector","properties":{"connector":{"type":"object"}},"required":["connector"]},{"type":"object","title":"kb","properties":{"kb":{"type":"object"}},"required":["kb"]},{"type":"object","title":"sandbox","properties":{"sandbox":{"type":"object"}},"required":["sandbox"]},{"type":"object","title":"webSearch","properties":{"webSearch":{"type":"object"}},"required":["webSearch"]},{"type":"object","title":"webFetch","properties":{"webFetch":{"type":"object"}},"required":["webFetch"]},{"type":"object","title":"agent","properties":{"agent":{"type":"object"}},"required":["agent"]},{"type":"object","title":"handover","properties":{"handover":{"type":"object"}},"required":["handover"]},{"type":"object","title":"delegation","properties":{"delegation":{"type":"object"}},"required":["delegation"]},{"type":"object","title":"transfer","properties":{"transfer":{"type":"object"}},"required":["transfer"]},{"type":"object","title":"suggestions","properties":{"suggestions":{"type":"object"}},"required":["suggestions"]},{"type":"object","title":"hangup","properties":{"hangup":{"type":"object"}},"required":["hangup"]},{"type":"object","title":"wait","properties":{"wait":{"type":"object"}},"required":["wait"]}]},"description":"Capabilities offered to the model *in addition* to `engine.ai.capabilities` while this step runs,\n configured exactly as they are there — the same union, the same structures. Use it to give a step reach\n it should not have everywhere: authoring the refund tool on the refund step, rather than on the agent, is\n also what keeps it out of every other step.\n\n Additive only, by design. A step cannot drop an agent-level capability, so it cannot strand a session by\n taking away the `handover` or `transfer` its channel needs, and the runtime keeps one rule for the turn's\n tool set. Skills the model loads (`engine.ai.skills`) and the channel's own built-ins join it either way."}},"required":["goal"]}},"required":["conversation"]},{"type":"object","title":"message","properties":{"message":{"type":"object","description":"Say one thing — spoken on voice, posted on chat / assistant — then move on. The line is either authored verbatim or generated at the step; either way the agent says it once and the transitions are evaluated, with no turn handed to the user.","properties":{"content":{"description":"Where the step's line comes from. Exactly one member is set: `text` is authored, `generate` is written by the model at the step.","oneOf":[{"type":"object","title":"text","properties":{"text":{"type":"string","maxLength":4000,"minLength":1,"description":"The line, verbatim. No model in the loop, which is what makes it the right content for a disclosure, a policy sentence or a legally worded confirmation."}},"required":["text"]},{"type":"object","title":"generate","properties":{"generate":{"type":"object"}},"required":["generate"]}]},"channels":{"type":"object","description":"Per-channel settings of a message step: how the line is protected when spoken, and what is offered with it when posted.","properties":{"voice":{"type":"object"},"chat":{"type":"object"}}}},"required":["content"]}},"required":["message"]},{"type":"object","title":"action","properties":{"action":{"type":"object","description":"A step that performs one capability. The runtime performs it on arrival — no model decision about *whether*\n to act, and no tool offered to the model for it. Which is the point: an action the graph reached is an action\n that happens, at the cost of nothing.","properties":{"capability":{"description":"The capability a step performs, run by the runtime rather than chosen by the model. A subset of\n AgentCapability, reusing its structures verbatim: these are the members that describe a single action with a\n beginning and an end. The rest of AgentCapability (`kb`, `webSearch`, `webFetch`, `sandbox`, `suggestions`)\n describes reach the model draws on while it talks, so it belongs on a conversation step's `capabilities`\n rather than on a step of its own.\n\n Configuration comes from the capability itself and nothing is added here: a tool step's fixed arguments are\n `AgentTool.fields` predefined handlers, its spoken lead-in is `AgentTool.startMessage`, a transfer's line\n before the hand-off is `AgentTransferPipeline.instructions`, an invoked agent's inputs are\n `AgentCallCapability.variables`. Anything left unpinned is filled by the model at the step, per the tool's\n input schema.","oneOf":[{"type":"object","title":"tool","properties":{"tool":{"type":"object"}},"required":["tool"]},{"type":"object","title":"connector","properties":{"connector":{"type":"object"}},"required":["connector"]},{"type":"object","title":"agent","properties":{"agent":{"type":"object"}},"required":["agent"]},{"type":"object","title":"handover","properties":{"handover":{"type":"object"}},"required":["handover"]},{"type":"object","title":"delegation","properties":{"delegation":{"type":"object"}},"required":["delegation"]},{"type":"object","title":"transfer","properties":{"transfer":{"type":"object"}},"required":["transfer"]},{"type":"object","title":"hangup","properties":{"hangup":{"type":"object"}},"required":["hangup"]},{"type":"object","title":"wait","properties":{"wait":{"type":"object"}},"required":["wait"]}]},"forceToolCall":{"type":"boolean","description":"Run the step as one model call with the tool choice pinned to this capability, instead of calling it\n straight from the runtime. The model still cannot decide *whether* to call it — only what to put in the\n arguments left unpinned, which is what a lookup keyed on something nobody can template ('the symptom the\n caller just described') needs.\n\n Costs one model call where the default costs none, so it is opt-in. Honoured where the action has\n arguments a model can write — `tool`, `connector`, `agent`, and a `transfer` whose target the model\n chooses; ignored elsewhere. Leaving it false while a required argument is unpinned is an authoring error,\n not a runtime one: nothing would be there to fill it."},"output":{"type":"string","maxLength":128,"minLength":1,"pattern":"^[a-zA-Z0-9_]+$","description":"Workflow variable the action's result is written to, so later `expression` conditions can branch on it and\n later steps can template it (e.g. capture a lookup into `policy`, then branch on\n `{ \"==\": [{ \"var\": \"policy.status\" }, \"active\"] }`). Absent means the result is not captured; the\n model still sees it in the conversation per the capability's own pipeline.\n\n Written on success only. A failed action leaves the variable as it was and takes a `failure` transition\n instead — see AgentWorkflowFailureCondition — so a graph can never mistake an error for a result."}},"required":["capability"]}},"required":["action"]},{"type":"object","title":"variables","properties":{"variables":{"type":"object","description":"Write workflow variables. The step nobody asks for until the first time a graph has to remember something between two branches.","properties":{"assignments":{"type":"array","items":{"type":"object","description":"One `name = value` write. Exactly one of `value` or `expression` is set: `value` renders text, `expression` computes.","properties":{"name":{"type":"string","maxLength":128,"minLength":1,"pattern":"^[a-zA-Z0-9_]+$"},"value":{"type":"string","maxLength":4000,"description":"Text written to the variable, with handlebars resolved against workflow variables, agent variables and session context (e.g. '{{lookup.status}}'). Empty clears the variable."},"expression":{"description":"A JsonLogic expression computing the value instead of rendering it, which is what makes arithmetic possible: `{ \"+\": [{ \"var\": \"attempts\" }, 1] }` is the retry counter a bounded loop needs, written with no model in the loop."}},"required":["name"]},"maxItems":50,"minItems":1}},"required":["assignments"]}},"required":["variables"]},{"type":"object","title":"workflow","properties":{"workflow":{"type":"object","description":"Run a nested workflow and continue once it returns.\n\n The unit of decomposition: a twelve-step identity check is called from the front door, from the booking flow\n and from the complaints line, so it is authored once and referenced by a single step at each call site. The\n nested workflow shares this session and its variables; it returns through its `result` steps, which is what\n the calling step's `result` transitions branch on. A nested workflow that instead reaches an `end` or a\n terminal action does what it says — the session is over and nothing comes back.","properties":{"workflow":{"type":"string","maxLength":64,"minLength":1,"pattern":"^[a-zA-Z0-9_-]+$","description":"The nested workflow to run. Must be declared in `AgentWorkflow.nested`, and must not run itself directly or through a ring of nested workflows."},"output":{"type":"string","maxLength":128,"minLength":1,"pattern":"^[a-zA-Z0-9_]+$","description":"Workflow variable the nested workflow's result `value` is written to. Absent means the value is discarded — which is the usual case when the branch taken is the whole answer."}},"required":["workflow"]}},"required":["workflow"]},{"type":"object","title":"result","properties":{"result":{"type":"object","description":"End this workflow and hand control back to whatever invoked it.\n\n In a nested workflow the invoking step resumes and branches on which result was reached (see\n AgentWorkflowResultCondition). In the main graph of a workflow-channel agent the run finishes and the result\n is returned to the caller — the agent, automation or API request that started it. In the main graph of a\n voice / chat / assistant agent there is no invoker, so it behaves as `end`.","properties":{"value":{"type":"string","maxLength":8000,"minLength":1,"description":"What this result returns to the invoker. Supports handlebars resolved against workflow variables, agent variables and session context (e.g. '{{policy.number}}'). Absent returns the result's id alone, which is enough when the caller only branches on which result was reached."}}}},"required":["result"]},{"type":"object","title":"end","properties":{"end":{"type":"object","description":"The session is over. Terminal and configuration-free by design: a closing line is a `message` step in front of it, and releasing a call is the `hangup` action — both already exist, and neither needs restating here."}},"required":["end"]}]},"transitions":{"type":"array","items":{"type":"object","description":"One way out of a step.","properties":{"target":{"type":"string","maxLength":64,"minLength":1,"pattern":"^[a-zA-Z0-9_-]+$","description":"Step to move to. Must be a step of the same workflow — a nested workflow is entered through a `workflow` step, not by pointing a transition into it."},"condition":{"description":"How the runtime decides to take a transition. Exactly one member is set.","oneOf":[{"type":"object","title":"always","properties":{"always":{"type":"object","description":"Unconditional. Only legal as a step's single transition: two unconditional exits are a coin toss, not a routing decision."}},"required":["always"]},{"type":"object","title":"prompt","properties":{"prompt":{"type":"object","description":"A condition the model judges against the conversation, e.g. 'the caller has said they will try the fix'.\n\n Phrase it as something the **caller** has done. A condition describing what the *agent* has done — 'the\n caller has a next step to try' — is true the instant the agent names one, so the model leaves before the\n caller has answered, and one early exit cascades through the rest of the graph. Rewording the same exits from\n agent actions to caller actions, changing nothing else, moved three models from 58–64 % of turns on the right\n step to 96–100 %, and removed every stray move. It is the single largest effect measured on this contract, so\n an authoring surface that accepts an agent-action condition is the defect, not the wording.","properties":{"instructions":{"type":"string","maxLength":2000,"minLength":1}},"required":["instructions"]}},"required":["prompt"]},{"type":"object","title":"expression","properties":{"expression":{"type":"object","description":"A branch taken when workflow state satisfies the condition — no model judgement involved, which is what makes it the right condition after an `action` or `variables` step.","properties":{"conditions":{"description":"A JsonLogic condition tree evaluated against workflow state, e.g.\n `{ \"==\": [ { \"var\": \"lookup.status\" }, \"found\" ] }`. The `var` namespace is the workflow variables\n written by `variables` steps and action `output` captures, the agent's input variables\n (`engine.ai.variables`), and the session context. An empty tree always matches. This is a\n separate language from the rule-tree AutomationConditions (wilma-workflows) and CopilotConditions\n (wilma-copilot)."}},"required":["conditions"]}},"required":["expression"]},{"type":"object","title":"result","properties":{"result":{"type":"object","description":"A branch on which result a nested workflow returned. Only meaningful on a `workflow` step; the referenced step must be a `result` step of the nested workflow that step runs.","properties":{"result":{"type":"string","maxLength":64,"minLength":1,"pattern":"^[a-zA-Z0-9_-]+$","description":"Id of the `result` step reached inside the nested workflow. An id rather than a name, so renaming a result from 'failed' to 'gave up' does not unwire every step that handles it."}},"required":["result"]}},"required":["result"]},{"type":"object","title":"failure","properties":{"failure":{"type":"object","description":"A branch taken when the step's action fails. Only meaningful on an `action` step, and the only condition a\n terminal action may carry: a transfer that finds the extension busy has not left the session, so a graph that\n cannot say what happens next simply drops the caller.\n\n Failure short-circuits — when an action fails, only `failure` transitions are considered, never the\n `expression` or `prompt` branches that were written for its result. If none matches, the session fails with\n the action's error, so a step whose capability can fail wants a catch-all last in the list.","properties":{"kinds":{"type":"array","items":{"type":"object"},"description":"Kinds this branch handles, evaluated as a set. Absent or empty catches **any** failure — the catch-all, which the list order must therefore place after the specific branches."}}}},"required":["failure"]},{"type":"object","title":"fallback","properties":{"fallback":{"type":"object","description":"The else-branch, taken when no sibling matched. At most one per step, and reached last regardless of where it sits in the list."}},"required":["fallback"]}]}},"required":["condition","target"]},"description":"The ways out of this step. Must be non-empty on every step that stays in the session, since a step the\n runtime cannot leave is a dead end.\n\n On `result` and `end` it must be empty. On the `action` members that leave the session (`handover`,\n `delegation`, `transfer`, `hangup`) it may carry `failure` transitions and nothing else: an action that\n was supposed to leave and failed — a busy extension, a service desk with nobody on it — has not left, and\n a graph with no answer for that drops the caller."},"position":{"type":"object","description":"Canvas coordinates of a step in the builder. Authoring metadata: the runtime ignores it, and a workflow that arrives without positions is laid out automatically.","properties":{"x":{"type":"number"},"y":{"type":"number"}},"required":["x","y"]}},"required":["handler","id","label"]},"maxItems":200,"minItems":1,"description":"Steps of one graph. Ids are unique within it."},"id":{"type":"string","maxLength":64,"minLength":1,"pattern":"^[a-zA-Z0-9_-]+$","description":"Identifier of a nested workflow within the agent's workflow."},"name":{"type":"string","maxLength":128,"minLength":1},"description":{"type":"string","maxLength":1000,"minLength":1,"description":"One line saying what the nested workflow is for, shown on the step that runs it."}},"required":["entry","id","name","steps"]},"description":"Graphs the main workflow can run, referenced by `workflow` steps."},"settings":{"type":"object","properties":{"maxTransitions":{"type":"number","maximum":500,"minimum":1,"description":"Give up after this many step transitions, so a cycle whose conditions all keep matching cannot run\n forever. Absent means no limit, which is the default: a deliberate loop — 'keep asking until you have the\n postcode' — is a normal shape, and silently capping it would be worse than making the author say so."},"variables":{"type":"array","items":{"type":"object","description":"One `name = value` write. Exactly one of `value` or `expression` is set: `value` renders text, `expression` computes.","properties":{"name":{"type":"string","maxLength":128,"minLength":1,"pattern":"^[a-zA-Z0-9_]+$"},"value":{"type":"string","maxLength":4000,"description":"Text written to the variable, with handlebars resolved against workflow variables, agent variables and session context (e.g. '{{lookup.status}}'). Empty clears the variable."},"expression":{"description":"A JsonLogic expression computing the value instead of rendering it, which is what makes arithmetic possible: `{ \"+\": [{ \"var\": \"attempts\" }, 1] }` is the retry counter a bounded loop needs, written with no model in the loop."}},"required":["name"]},"description":"Workflow variables seeded before the first step runs. Agent input variables (`engine.ai.variables`) are already in scope and need no entry here."},"layout":{"type":"string","description":"How the builder arranges the graph. Authoring metadata: the runtime ignores it. `stages` bands the graph by `stage`, which is what a forty-step workflow needs.","enum":["flow","compact","stages"]}}}},"required":["entry","steps"]},"guardrails":{"type":"object","description":"Turn screening, by side. Only `input` exists: it guards what reaches the model, and output screening is a separate policy with different actions, added here when it is built.","properties":{"input":{"type":"object","description":"Screening applied to what reaches the model.\n\n There is deliberately no bundle or preset-group member. A named group that expands to a set of checks\n reads like convenience and behaves like version skew: the same agent definition would mean different\n things before and after a platform release, invisibly to the tenant who published it. Listing the checks\n costs a few lines in a document the console writes anyway, and the published version then states exactly\n what it enforces.\n\n There is also no set-level default action. One field standing behind every check reads as a convenience\n and hides the thing a reader most needs to see: what a given check will actually do. A check's action is\n its own or its preset's, and both are visible on the check.","properties":{"checks":{"type":"array","items":{"type":"object","description":"One thing the checker is asked about a turn. There is deliberately no `enabled` flag: a check that\n should not run is removed, so a published version never carries a switched-off check that reads like\n policy.","properties":{"name":{"type":"string","maxLength":64,"minLength":1,"description":"Name of a check, unique within a guardrail set. Stable and machine-facing: session events and trace spans report a verdict by this name, so a reader needs no copy of the agent definition to tell which check fired."},"description":{"type":"string"},"kind":{"description":"What a check is: one of the platform's presets, or a prompt the tenant wrote. Exactly one member is set.","oneOf":[{"type":"object","title":"preset","properties":{"preset":{"type":"object","description":"A check backed by one of the platform's prebuilt prompts.","properties":{"preset":{"type":"string","description":"A prebuilt check. Each preset is a platform-maintained prompt shipped in code and versioned with the\n platform, not a tenant row — so a support question about what a preset caught has one answer. A preset\n check is otherwise an ordinary AgentGuardrailCheck: its sensitivity and action are the tenant's to set,\n and `notes` narrows its prompt without replacing it.","enum":["jailbreak","offTopic","abuse","harmful","selfHarm","pii","secrets","regulatedAdvice"]},"notes":{"type":"string","maxLength":2000,"minLength":1,"description":"Appended to the preset's prompt. For narrowing, not redefining — 'ignore card numbers read back after our own IVR prompts for them' — so the preset keeps its maintenance while fitting a tenant whose product would otherwise fight it."}},"required":["preset"]}},"required":["preset"]},{"type":"object","title":"custom","properties":{"custom":{"type":"object","description":"A check written by the tenant. A name and a prompt is the whole surface: no category enum can express the rules customers actually buy, such as refusing to quote a price before qualifying.","properties":{"instructions":{"type":"string","maxLength":10000,"minLength":1,"description":"Natural-language description of what a custom check looks for. Describe the *content* to be caught, not an instruction to the checker: 'The caller is asking about cryptocurrency investments' works, 'Block all content about crypto' does not, and a negative definition ('anything except billing questions') reliably misfires."}},"required":["instructions"]}},"required":["custom"]}]},"sensitivity":{"type":"string","description":"How much confidence a verdict needs before it counts as tripped. The checker reports a confidence per\n check and this is the threshold against it, so raising sensitivity catches more and costs more false\n positives without changing the prompt or the model: `low` trips only on high confidence, `medium` on\n high and medium, `high` on anything but a clear pass. Defaults to MEDIUM.","enum":["low","medium","high"]},"action":{"description":"What happens when a check trips. Exactly one member is set.\n\n Ordered by severity: observe < redact < block < end. When more than one check trips in the same call the\n most severe action wins and ties break on the order of `checks`, so a set can mix an observed check with\n an enforced one without the observation weakening the enforcement.\n\n There is no hand-over member yet. Handing a session to a human is a different mechanism on every channel\n — a voice transfer, a chat assignment, an assistant with no human behind it — and wiring a guardrail into\n all of them is out of proportion to the rest of this feature. Until AgentHandoverCapability has a runtime\n on more than one channel, the crisis path is a block whose message routes the caller.","oneOf":[{"type":"object","title":"observe","properties":{"observe":{"type":"object","description":"Record the verdict and let the turn through. The rollout mode: every guardrail should run here first, against real traffic, until its trips are read and its sensitivity is tuned. A tripped check still emits GuardrailTrippedEvent and still appears in the trace, which is what makes this a tuning tool rather than a switched-off check."}},"required":["observe"]},{"type":"object","title":"redact","properties":{"redact":{"type":"object","description":"Mask the matched spans and let the turn continue. The mask is chosen by the platform and is not configurable — a tenant-authored placeholder that collides with real text, or that the model reads as an instruction, is worse than having no choice. Only valid on checks that return spans (PII, SECRETS)."}},"required":["redact"]},{"type":"object","title":"block","properties":{"block":{"type":"object","description":"Refuse the turn. The agent's model is never called, no tool runs, and the caller is answered with `message` instead. The session stays alive and the conversation continues, so on a check meant to redirect rather than stop — 'let me take a few details first' — the message is doing the work.","properties":{"message":{"type":"string","maxLength":1000,"minLength":1,"description":"Said to the caller in place of the agent's reply. Falls back to a channel default when unset."}}}},"required":["block"]},{"type":"object","title":"end","properties":{"end":{"type":"object","description":"End the session. It lands in SessionStatus TERMINATED — not STOPPED, which is what a user asking to stop means — so a refused session is never read back as a clean finish. On voice this hangs up the call; on the other channels the session simply accepts no further turns.","properties":{"message":{"type":"string","maxLength":1000,"minLength":1,"description":"Said to the caller before the session ends."}}}},"required":["end"]}]}},"required":["kind","name"]},"maxItems":30,"description":"The checks to run, evaluated together in one call. Absent or empty means no screening."},"model":{"type":"string","pattern":"^(default|([a-z0-9][a-z0-9-]*://[a-zA-Z0-9._/-]+(\\?[a-zA-Z0-9._&=%-]*)?))$","description":"The model that runs the checks. Defaults to the platform's guardrail model — deliberately small and fast, and deliberately not the agent's own, whose cost and latency are what this exists to avoid spending on a bad turn."},"action":{"description":"What happens when a check trips. Exactly one member is set.\n\n Ordered by severity: observe < redact < block < end. When more than one check trips in the same call the\n most severe action wins and ties break on the order of `checks`, so a set can mix an observed check with\n an enforced one without the observation weakening the enforcement.\n\n There is no hand-over member yet. Handing a session to a human is a different mechanism on every channel\n — a voice transfer, a chat assignment, an assistant with no human behind it — and wiring a guardrail into\n all of them is out of proportion to the rest of this feature. Until AgentHandoverCapability has a runtime\n on more than one channel, the crisis path is a block whose message routes the caller.","oneOf":[{"type":"object","title":"observe","properties":{"observe":{"type":"object","description":"Record the verdict and let the turn through. The rollout mode: every guardrail should run here first, against real traffic, until its trips are read and its sensitivity is tuned. A tripped check still emits GuardrailTrippedEvent and still appears in the trace, which is what makes this a tuning tool rather than a switched-off check."}},"required":["observe"]},{"type":"object","title":"redact","properties":{"redact":{"type":"object","description":"Mask the matched spans and let the turn continue. The mask is chosen by the platform and is not configurable — a tenant-authored placeholder that collides with real text, or that the model reads as an instruction, is worse than having no choice. Only valid on checks that return spans (PII, SECRETS)."}},"required":["redact"]},{"type":"object","title":"block","properties":{"block":{"type":"object","description":"Refuse the turn. The agent's model is never called, no tool runs, and the caller is answered with `message` instead. The session stays alive and the conversation continues, so on a check meant to redirect rather than stop — 'let me take a few details first' — the message is doing the work.","properties":{"message":{"type":"string","maxLength":1000,"minLength":1,"description":"Said to the caller in place of the agent's reply. Falls back to a channel default when unset."}}}},"required":["block"]},{"type":"object","title":"end","properties":{"end":{"type":"object","description":"End the session. It lands in SessionStatus TERMINATED — not STOPPED, which is what a user asking to stop means — so a refused session is never read back as a clean finish. On voice this hangs up the call; on the other channels the session simply accepts no further turns.","properties":{"message":{"type":"string","maxLength":1000,"minLength":1,"description":"Said to the caller before the session ends."}}}},"required":["end"]}]},"mode":{"type":"string","description":"Runs the checker before the agent's model (BLOCKING) or alongside it (PARALLEL). Blocking costs the checker's latency on every turn but guarantees that a refused turn spent no tokens and fired no tool; parallel adds no latency and is the right trade on voice, where the delay is audible, at the risk that part of a reply reaches the caller and that a tool has already run before the cancellation lands. Defaults to BLOCKING.","enum":["blocking","parallel"]},"errorMode":{"type":"string","description":"What happens when the checker itself errors or times out. OPEN lets the turn through unscreened and records GuardrailFailedEvent, so an unscreened turn is a fact in the log rather than an absence; CLOSED refuses it. Defaults to OPEN: an agent that stops answering because a small model had a bad minute is a worse and far likelier outage than the rare turn that goes unscreened. Background tasks, where nobody is waiting and a retry is free, are the case for CLOSED.","enum":["open","closed"]},"window":{"type":"string","description":"How much of the conversation the checker sees. Defaults to LAST_WITH_PRIOR, because the jailbreaks worth catching are built across several turns — establish a fiction, then cash it in — and a checker shown only the last turn sees an innocuous sentence. There is no full-transcript option: screening a whole call on every turn would make the guardrail dearer than the agent.","enum":["lastMessage","lastWithPrior"]}}}}},"model":{"type":"string","pattern":"^(default|([a-z0-9][a-z0-9-]*://[a-zA-Z0-9._/-]+(\\?[a-zA-Z0-9._&=%-]*)?))$","description":"Defaults to the platform default model when omitted."},"timeZone":{"type":"string","maxLength":64,"minLength":1,"description":"IANA timezone the agent reasons about time in: it anchors the current date and time given to the model and how times are read from and written to tool arguments and replies. Shared by every channel the agent serves. Defaults to 'UTC'."},"artifacts":{"type":"boolean","description":"Whether the agent can produce artifacts (wilma-artifacts): files it writes in its sandbox and shares back during a session — a document, a PDF, a spreadsheet, a chart, a multi-page web site, a folder of files. Needs the sandbox capability. An artifact starts private to the user the session runs for, who can share it company-wide from the viewer; on the chat channel the runtime shares each one company-wide as it is created, so the link posted into the conversation opens for every signed-in colleague. Artifacts are company-internal: recipients on external transports (sms / whatsapp) or outside the company cannot open the links, so keep this disabled for customer-facing agents. Defaults to false."},"skills":{"type":"array","items":{"type":"object","description":"Reference to a skill attached to an agent.","properties":{"id":{"type":"string","maxLength":12,"minLength":12},"skillVersion":{"type":"number","minimum":1,"description":"Pin a specific published skill version. When omitted, latestVersion is resolved."},"access":{"type":"object","description":"Who is allowed to use the agent. There is no view/edit dimension — access solely grants the ability to use the agent.\n\n Where it is applied, per channel:\n - `assistant` — enforced. GetAgent on wilma-assistant refuses an agent the caller is not granted.\n - `voice` — deliberately not applied. Inbound calls are routed by the PBX dialplan, and the data ACL is the\n agent-level `settings.pbxGroupId`, not per-user grants.\n - `chat` — enforced for COLLEAGUES ONLY. wilma-chat checks the sender against this block when the sender\n belongs to the agent's own company. A sender from outside it — a Kite website visitor, an SMS or WhatsApp\n contact — is not governed by this field at all and reaches the agent under every visibility; what exposes\n an agent to them is an administrator wiring it to that surface. A refused colleague receives one short\n refusal message and no turn is started.\n A `groupId` grant is weaker here than on the assistant: this channel reads the sender's group from their\n x-bees user record, which does not always carry one. When EVERY grant on the agent is a `groupId` grant and\n the sender's group is unknown, the sender is PASSED rather than refused — refusing would lock out everyone\n whose record lacks a group, which is a large minority. Adding a `userId` or `department` grant removes that\n waiver and the agent is enforced normally, so use one of those where the restriction must hold on chat.\n (x-bees chat bots are a separate stack with their own company-level `BotAccess`; it is not this field.)\n\n When an agent is invoked as a background task its agent-level access is not used — invocation access is configured\n per reference on the calling agent (see AgentCallCapability.access).","properties":{"visibility":{"type":"string","enum":["explicit","organization","public"]},"grants":{"type":"array","items":{"description":"A user, group or department that access is granted to.","oneOf":[{"type":"object","title":"userId","properties":{"userId":{"type":"string","maxLength":12,"minLength":12}},"required":["userId"]},{"type":"object","title":"groupId","properties":{"groupId":{"type":"string"}},"required":["groupId"]},{"type":"object","title":"department","properties":{"department":{"type":"string","description":"PBX department NAME, as carried on the colleague record (`PbxColleague.department`), not a department id.\n The x-bees directory knows a colleague's department only by name, so matching by name is what lets the check\n read a field the runtime already has instead of resolving the PBX department tree on every request.\n Two consequences: renaming a department in the PBX invalidates every grant naming it, and two departments\n with the same name in different branches of the tree are one value here."}},"required":["department"]}]},"description":"Users, groups and departments granted the ability to use the agent. Applies when visibility is `explicit`."}}}},"required":["id"]},"description":"Skills attached to this agent (progressive disclosure). A skill may carry its own tools, which join this agent's toolset while that skill is active."},"capabilities":{"type":"array","items":{"oneOf":[{"type":"object","title":"tool","properties":{"tool":{"type":"object","properties":{"id":{"type":"string","maxLength":12,"minLength":12,"pattern":"^[0-9A-Za-z]{12}$","description":"Unique identifier of the tool using nanoid format"},"toolVersion":{"type":"number","minimum":1,"description":"Pin a specific published tool version. When omitted, the tool's latestVersion is resolved at load, so a republished tool reaches this agent without a new agent version. Pin it when `fields` must keep aiming at an exact input schema."},"description":{"type":"string","maxLength":4096,"minLength":1,"description":"Override the tool's description for this agent. When omitted, the tool's own description is used."},"fields":{"type":"array","items":{"type":"object","description":"Per-field override applied to a shared tool's input schema for this agent.","properties":{"path":{"type":"string","maxLength":512,"minLength":1,"pattern":"^/.*$","description":"JSON Pointer to the target property in the tool input schema."},"handler":{"oneOf":[{"type":"object","title":"guided","properties":{"guided":{"type":"object","description":"Keeps the field model-fillable but overrides its description in the schema shown to the model.","properties":{"description":{"type":"string","maxLength":1024,"minLength":1}},"required":["description"]}},"required":["guided"]},{"type":"object","title":"predefined","properties":{"predefined":{"type":"object","description":"A fixed value injected at the field path and excluded from the schema shown to the model, so the model neither sees nor sets it. A JSON value, or a string with handlebars templates resolved against agent variables and session context (e.g. '{{callerName}}').","properties":{"value":{}},"required":["value"]}},"required":["predefined"]}]}},"required":["handler","path"]},"description":"Per-field overrides on the tool input schema: pin fixed values (hidden from the model) or refine field descriptions. Fields not listed are filled by the model per the tool schema. Paths are resolved against the input schema of the tool version in effect — pin `toolVersion` when they must not move."},"startMessage":{"type":"string","description":"Message spoken when the tool call starts. Honoured on the voice channel. Spoken in every language of the call while `startMessages` is absent or empty; not consulted once `startMessages` carries any entry."},"startMessages":{"type":"array","items":{"type":"object","description":"One phrase in one language, for a capability that speaks a fixed line on the voice channel.\n Which entry is spoken is decided by the call's current language — the one the call resolved\n to at connect, or the one automatic detection switched to — matched on `language`\n CASE-INSENSITIVELY, the same rule `VoiceBotSpokenPhrase` states for the legacy stack, so a\n code written in either case reaches the same entry on either. Blank `text` means the agent\n says nothing in that language; it does NOT fall back to the language-agnostic member beside\n the list, which would speak one language's text in another language's voice.","properties":{"language":{"type":"string","maxLength":8,"minLength":2,"pattern":"^[a-z]{2,3}(-[A-Z]{2})?$","description":"BCP-47 language tag, optionally with a region subtag: 'en', 'en-US', 'pt-BR'. Deliberately a\n constrained string rather than an enum: the offerable set is a property of the selected\n recogniser and changes with it, so an enumeration here would cap the set at whatever was known\n when the model was released."},"text":{"type":"string"}},"required":["language","text"]},"description":"Per-language start messages. When present and non-empty, the entry for the call's current\n language is spoken and `startMessage` is not consulted; a language with no entry is silent.\n When absent or empty, `startMessage` is spoken as before. On a call with no effective speech\n configuration — no configured languages, or a PBX below protocol 2 — `startMessage` is spoken\n regardless, because there is no current language to choose by."},"pipeline":{"type":"object","properties":{"type":{"type":"string","description":"How the model waits for a tool and whether it synthesizes a reply from the result.","enum":["blocking_request","blocking_request_guided","async_request","async_request_guided"]},"instructions":{"type":"string","description":"Reply instructions for the *_GUIDED pipeline types. Use {{response}} to reference the tool result."}}},"approval":{"type":"object","description":"Human-in-the-loop confirmation for a tool. Presence requires approval: the agent pauses before running the tool and the session user (whoever controls the session) approves or denies inline.","properties":{"prompt":{"type":"string","description":"Optional message shown in the confirmation prompt. Supports handlebars against agent variables and the pending tool arguments. When omitted, a default prompt is shown."}}},"channels":{"type":"array","items":{"type":"string","description":"A channel an agent can serve (see AgentChannels for enablement). Also the runtime channel recorded by the channel runtimes / wilma-traces. A background task has no delivery surface and therefore no channel.","enum":["voice","chat","assistant"]},"uniqueItems":true,"description":"Channels this capability is offered on. Absent or empty — the default — means every channel the agent serves\n that can honour it. Listing channels narrows that set and never widens it: a `transfer` listed for `chat` is\n still ignored there, because the chat channel has no call to transfer."},"annotations":{"type":"array","items":{"description":"Configuration for annotations emitted after a tool completes.","oneOf":[{"type":"object","title":"tag","properties":{"tag":{"type":"object","description":"Tag annotation emitted after a tool completes.","properties":{"value":{"type":"string","description":"Static handlebars expression resolved against agent variables and helpers."},"applyOnSuccess":{"type":"boolean","description":"Apply when the tool succeeds."},"applyOnFailure":{"type":"boolean","description":"Apply when the tool fails."}}}},"required":["tag"]},{"type":"object","title":"comment","properties":{"comment":{"type":"object","description":"Comment annotation emitted after a tool completes.","properties":{"value":{"type":"string","description":"Static handlebars expression resolved against agent variables and helpers."},"applyOnSuccess":{"type":"boolean","description":"Apply when the tool succeeds."},"applyOnFailure":{"type":"boolean","description":"Apply when the tool fails."}}}},"required":["comment"]},{"type":"object","title":"variables","properties":{"variables":{"type":"object","description":"Variables annotation emitted after a tool completes."}},"required":["variables"]},{"type":"object","title":"result","properties":{"result":{"type":"object","description":"Result annotation emitted after a tool completes."}},"required":["result"]}]}}},"required":["id"]}},"required":["tool"]},{"type":"object","title":"connector","properties":{"connector":{"type":"object","description":"A configured connector (wilma-tools) exposed to the model as a single meta-tool named\n after the connector. The model calls the reserved `help` action to learn the callable\n actions, then dispatches an action through the same tool. Progressive disclosure runs\n through the connector tool itself, not the skills catalogue.","properties":{"id":{"type":"string","maxLength":12,"minLength":12,"pattern":"^[0-9A-Za-z]{12}$","description":"Configured connector instance in wilma-tools. Its pinned definition, permission `grant` and authorization govern what the model can discover via `help` and call. There is no version pin here, unlike `AgentTool.toolVersion`: the action catalogue and its schemas are already frozen by the connector's pinned definition, while `grant` and authorization are read live so permission changes apply at once."},"startMessage":{"type":"string","description":"Message spoken when a connector action call starts. Honoured on the voice channel. Mirrors `AgentTool.startMessage`. Not applied to the read-only `help` action. Not consulted once `startMessages` carries any entry."},"startMessages":{"type":"array","items":{"type":"object","description":"One phrase in one language, for a capability that speaks a fixed line on the voice channel.\n Which entry is spoken is decided by the call's current language — the one the call resolved\n to at connect, or the one automatic detection switched to — matched on `language`\n CASE-INSENSITIVELY, the same rule `VoiceBotSpokenPhrase` states for the legacy stack, so a\n code written in either case reaches the same entry on either. Blank `text` means the agent\n says nothing in that language; it does NOT fall back to the language-agnostic member beside\n the list, which would speak one language's text in another language's voice.","properties":{"language":{"type":"string","maxLength":8,"minLength":2,"pattern":"^[a-z]{2,3}(-[A-Z]{2})?$","description":"BCP-47 language tag, optionally with a region subtag: 'en', 'en-US', 'pt-BR'. Deliberately a\n constrained string rather than an enum: the offerable set is a property of the selected\n recogniser and changes with it, so an enumeration here would cap the set at whatever was known\n when the model was released."},"text":{"type":"string"}},"required":["language","text"]},"description":"Forward-compatible per-language connector start messages, mirroring `AgentTool.startMessages`. Accepted and stored so clients can configure connector and tool phrases consistently, but currently ignored at execution time because the voice runtime does not read connector start messages yet. Runtime enablement is tracked in wilma-services docs/todo.md item 8."},"pipeline":{"type":"object","properties":{"type":{"type":"string","description":"How the model waits for a tool and whether it synthesizes a reply from the result.","enum":["blocking_request","blocking_request_guided","async_request","async_request_guided"]},"instructions":{"type":"string","description":"Reply instructions for the *_GUIDED pipeline types. Use {{response}} to reference the tool result."}}},"approval":{"type":"object","description":"Human-in-the-loop confirmation for a tool. Presence requires approval: the agent pauses before running the tool and the session user (whoever controls the session) approves or denies inline.","properties":{"prompt":{"type":"string","description":"Optional message shown in the confirmation prompt. Supports handlebars against agent variables and the pending tool arguments. When omitted, a default prompt is shown."}}},"channels":{"type":"array","items":{"type":"string","description":"A channel an agent can serve (see AgentChannels for enablement). Also the runtime channel recorded by the channel runtimes / wilma-traces. A background task has no delivery surface and therefore no channel.","enum":["voice","chat","assistant"]},"uniqueItems":true,"description":"Channels this capability is offered on. Absent or empty — the default — means every channel the agent serves\n that can honour it. Listing channels narrows that set and never widens it: a `transfer` listed for `chat` is\n still ignored there, because the chat channel has no call to transfer."},"annotations":{"type":"array","items":{"description":"Configuration for annotations emitted after a tool completes.","oneOf":[{"type":"object","title":"tag","properties":{"tag":{"type":"object","description":"Tag annotation emitted after a tool completes.","properties":{"value":{"type":"string","description":"Static handlebars expression resolved against agent variables and helpers."},"applyOnSuccess":{"type":"boolean","description":"Apply when the tool succeeds."},"applyOnFailure":{"type":"boolean","description":"Apply when the tool fails."}}}},"required":["tag"]},{"type":"object","title":"comment","properties":{"comment":{"type":"object","description":"Comment annotation emitted after a tool completes.","properties":{"value":{"type":"string","description":"Static handlebars expression resolved against agent variables and helpers."},"applyOnSuccess":{"type":"boolean","description":"Apply when the tool succeeds."},"applyOnFailure":{"type":"boolean","description":"Apply when the tool fails."}}}},"required":["comment"]},{"type":"object","title":"variables","properties":{"variables":{"type":"object","description":"Variables annotation emitted after a tool completes."}},"required":["variables"]},{"type":"object","title":"result","properties":{"result":{"type":"object","description":"Result annotation emitted after a tool completes."}},"required":["result"]}]}}},"required":["id"]}},"required":["connector"]},{"type":"object","title":"kb","properties":{"kb":{"type":"object","properties":{"knowledgeBaseId":{"type":"string"},"instructions":{"type":"string","maxLength":60000,"minLength":1,"description":"Additional instructions for when the agent should use this knowledge base."},"searchStrategy":{"type":"string","description":"Search algorithm used to retrieve passages.","enum":["bm25","vector","hybrid"]},"searchThreshold":{"type":"string","description":"Minimum similarity a passage needs to be returned. Raising it trades recall for precision.","enum":["low","medium","high"]},"searchLimit":{"type":"number","maximum":50,"minimum":1,"description":"Maximum number of passages this capability returns. Defaults to the knowledge base service's own default (10) when omitted. Keep it low on voice: every passage is prompt the model reads before it can speak."},"channels":{"type":"array","items":{"type":"string","description":"A channel an agent can serve (see AgentChannels for enablement). Also the runtime channel recorded by the channel runtimes / wilma-traces. A background task has no delivery surface and therefore no channel.","enum":["voice","chat","assistant"]},"uniqueItems":true,"description":"Channels this capability is offered on. Absent or empty — the default — means every channel the agent serves\n that can honour it. Listing channels narrows that set and never widens it: a `transfer` listed for `chat` is\n still ignored there, because the chat channel has no call to transfer."},"annotations":{"type":"array","items":{"description":"Configuration for annotations emitted after a tool completes.","oneOf":[{"type":"object","title":"tag","properties":{"tag":{"type":"object","description":"Tag annotation emitted after a tool completes.","properties":{"value":{"type":"string","description":"Static handlebars expression resolved against agent variables and helpers."},"applyOnSuccess":{"type":"boolean","description":"Apply when the tool succeeds."},"applyOnFailure":{"type":"boolean","description":"Apply when the tool fails."}}}},"required":["tag"]},{"type":"object","title":"comment","properties":{"comment":{"type":"object","description":"Comment annotation emitted after a tool completes.","properties":{"value":{"type":"string","description":"Static handlebars expression resolved against agent variables and helpers."},"applyOnSuccess":{"type":"boolean","description":"Apply when the tool succeeds."},"applyOnFailure":{"type":"boolean","description":"Apply when the tool fails."}}}},"required":["comment"]},{"type":"object","title":"variables","properties":{"variables":{"type":"object","description":"Variables annotation emitted after a tool completes."}},"required":["variables"]},{"type":"object","title":"result","properties":{"result":{"type":"object","description":"Result annotation emitted after a tool completes."}},"required":["result"]}]}}}}},"required":["kb"]},{"type":"object","title":"sandbox","properties":{"sandbox":{"description":"Embedded sandbox: shell + file tools backed by an execution environment. The only capability with no `channels` list — being a union it has nowhere to carry one — so it is offered on every channel the agent serves.","oneOf":[{"type":"object","title":"inline","properties":{"inline":{"type":"object","description":"Sandbox configured inline on the agent, without a pre-built snapshot.","properties":{"size":{"type":"string","description":"Compute size of each sandbox launched from the snapshot. Each level maps to a platform-defined vCPU/memory/disk allocation.","enum":["min","small","large","max"]},"network":{"type":"string","description":"Network access policy for the sandbox.","enum":["allow-all","deny-all","allowlist"]},"allowedDomains":{"type":"array","items":{"type":"string","maxLength":253,"minLength":1},"description":"Domains allowed when network = ALLOWLIST."}}}},"required":["inline"]},{"type":"object","title":"snapshot","properties":{"snapshot":{"type":"object","description":"Sandbox launched from a pre-built snapshot managed by the wilma-sandbox service.","properties":{"snapshotId":{"type":"string","maxLength":12,"minLength":12,"description":"Snapshot to launch sandboxes from. Managed in the wilma-sandbox service; must be READY."},"network":{"type":"string","description":"Network access policy for the sandbox.","enum":["allow-all","deny-all","allowlist"]},"allowedDomains":{"type":"array","items":{"type":"string","maxLength":253,"minLength":1},"description":"Domains allowed when network = ALLOWLIST."},"size":{"type":"string","description":"Compute size of each sandbox launched from the snapshot. Each level maps to a platform-defined vCPU/memory/disk allocation.","enum":["min","small","large","max"]}},"required":["snapshotId"]}},"required":["snapshot"]}]}},"required":["sandbox"]},{"type":"object","title":"webSearch","properties":{"webSearch":{"type":"object","description":"Model-native web search. The provider tool is selected from the agent model.","properties":{"contextSize":{"type":"string","description":"Search depth/context hint, mapped per provider.","enum":["low","medium","high"]},"allowedDomains":{"type":"array","items":{"type":"string","maxLength":253,"minLength":1},"description":"Restrict results to these domains where the provider supports it."},"userLocation":{"type":"object","properties":{"country":{"type":"string","description":"ISO 3166-1 alpha-2 country code, e.g. 'US'."},"region":{"type":"string"},"city":{"type":"string"},"timezone":{"type":"string","description":"IANA timezone, e.g. 'Europe/Rome'."}}},"channels":{"type":"array","items":{"type":"string","description":"A channel an agent can serve (see AgentChannels for enablement). Also the runtime channel recorded by the channel runtimes / wilma-traces. A background task has no delivery surface and therefore no channel.","enum":["voice","chat","assistant"]},"uniqueItems":true,"description":"Channels this capability is offered on. Absent or empty — the default — means every channel the agent serves\n that can honour it. Listing channels narrows that set and never widens it: a `transfer` listed for `chat` is\n still ignored there, because the chat channel has no call to transfer."}}}},"required":["webSearch"]},{"type":"object","title":"webFetch","properties":{"webFetch":{"type":"object","description":"Server-side fetch of a URL, returning its readable text/markdown to the model.","properties":{"allowedDomains":{"type":"array","items":{"type":"string","maxLength":253,"minLength":1},"description":"Restrict fetches to these domains. Empty list means any domain is allowed."},"channels":{"type":"array","items":{"type":"string","description":"A channel an agent can serve (see AgentChannels for enablement). Also the runtime channel recorded by the channel runtimes / wilma-traces. A background task has no delivery surface and therefore no channel.","enum":["voice","chat","assistant"]},"uniqueItems":true,"description":"Channels this capability is offered on. Absent or empty — the default — means every channel the agent serves\n that can honour it. Listing channels narrows that set and never widens it: a `transfer` listed for `chat` is\n still ignored there, because the chat channel has no call to transfer."}}}},"required":["webFetch"]},{"type":"object","title":"agent","properties":{"agent":{"type":"object","description":"Invoke another agent as a dedicated execution with its own state. The called agent runs independently and returns its result.","properties":{"agentId":{"type":"string","maxLength":12,"minLength":12},"agentVersion":{"type":"number","minimum":1,"description":"Pin a specific published version of the called agent. When absent, the called agent's deployment configuration selects the version at invocation time."},"access":{"type":"object","description":"Who is allowed to use the agent. There is no view/edit dimension — access solely grants the ability to use the agent.\n\n Where it is applied, per channel:\n - `assistant` — enforced. GetAgent on wilma-assistant refuses an agent the caller is not granted.\n - `voice` — deliberately not applied. Inbound calls are routed by the PBX dialplan, and the data ACL is the\n agent-level `settings.pbxGroupId`, not per-user grants.\n - `chat` — enforced for COLLEAGUES ONLY. wilma-chat checks the sender against this block when the sender\n belongs to the agent's own company. A sender from outside it — a Kite website visitor, an SMS or WhatsApp\n contact — is not governed by this field at all and reaches the agent under every visibility; what exposes\n an agent to them is an administrator wiring it to that surface. A refused colleague receives one short\n refusal message and no turn is started.\n A `groupId` grant is weaker here than on the assistant: this channel reads the sender's group from their\n x-bees user record, which does not always carry one. When EVERY grant on the agent is a `groupId` grant and\n the sender's group is unknown, the sender is PASSED rather than refused — refusing would lock out everyone\n whose record lacks a group, which is a large minority. Adding a `userId` or `department` grant removes that\n waiver and the agent is enforced normally, so use one of those where the restriction must hold on chat.\n (x-bees chat bots are a separate stack with their own company-level `BotAccess`; it is not this field.)\n\n When an agent is invoked as a background task its agent-level access is not used — invocation access is configured\n per reference on the calling agent (see AgentCallCapability.access).","properties":{"visibility":{"type":"string","enum":["explicit","organization","public"]},"grants":{"type":"array","items":{"description":"A user, group or department that access is granted to.","oneOf":[{"type":"object","title":"userId","properties":{"userId":{"type":"string","maxLength":12,"minLength":12}},"required":["userId"]},{"type":"object","title":"groupId","properties":{"groupId":{"type":"string"}},"required":["groupId"]},{"type":"object","title":"department","properties":{"department":{"type":"string","description":"PBX department NAME, as carried on the colleague record (`PbxColleague.department`), not a department id.\n The x-bees directory knows a colleague's department only by name, so matching by name is what lets the check\n read a field the runtime already has instead of resolving the PBX department tree on every request.\n Two consequences: renaming a department in the PBX invalidates every grant naming it, and two departments\n with the same name in different branches of the tree are one value here."}},"required":["department"]}]},"description":"Users, groups and departments granted the ability to use the agent. Applies when visibility is `explicit`."}}},"variables":{"type":"array","items":{"type":"object","properties":{"name":{"type":"string","maxLength":128,"minLength":1,"pattern":"^[a-zA-Z0-9_]+$","description":"Name of the tool variable, e.g.: 'myvar'"},"handler":{"oneOf":[{"type":"object","title":"auto","properties":{"auto":{"type":"object","description":"The model fills this variable itself, from conversation context. Included in the tool's input schema."}},"required":["auto"]},{"type":"object","title":"guided","properties":{"guided":{"type":"object","description":"The model fills this variable, guided by this description. Included in the tool's input schema with the description attached.","properties":{"description":{"type":"string","maxLength":1024,"minLength":1}},"required":["description"]}},"required":["guided"]},{"type":"object","title":"predefined","properties":{"predefined":{"type":"object","description":"A fixed value bound to the variable and injected directly into the tool call. Not exposed to the model (excluded from the tool's input schema). Supports handlebars templates resolved against agent variables and session context (e.g. '{{callerName}}').","properties":{"value":{"type":"string"}},"required":["value"]}},"required":["predefined"]}]}},"required":["name"]}},"channels":{"type":"array","items":{"type":"string","description":"A channel an agent can serve (see AgentChannels for enablement). Also the runtime channel recorded by the channel runtimes / wilma-traces. A background task has no delivery surface and therefore no channel.","enum":["voice","chat","assistant"]},"uniqueItems":true,"description":"Channels this capability is offered on. Absent or empty — the default — means every channel the agent serves\n that can honour it. Listing channels narrows that set and never widens it: a `transfer` listed for `chat` is\n still ignored there, because the chat channel has no call to transfer."}},"required":["agentId"]}},"required":["agent"]},{"type":"object","title":"handover","properties":{"handover":{"type":"object","description":"Hand the conversation over to a human and leave it. Honoured on conversation channels (chat, assistant).","properties":{"name":{"type":"string","description":"Tool name exposed to the model. Auto-generated when omitted."},"description":{"type":"string","description":"Description for the model to decide when to hand over."},"variant":{"type":"string","enum":["agent","service"]},"agent":{"type":"string","maxLength":12,"minLength":12,"description":"Wilma agent to hand over to when variant = AGENT."},"agentVersion":{"type":"number","minimum":1,"description":"Pin a specific published version of the target agent when variant = AGENT. When absent, the target's deployment configuration selects the version at handover time."},"service":{"type":"string","description":"x-bees service id to hand over to when variant = SERVICE."},"silent":{"type":"boolean","description":"Hand over without posting a visible message to the user."},"pipeline":{"type":"object","properties":{"type":{"type":"string","enum":["handover","reply_and_handover","reply_instructed_and_handover"]},"instructions":{"type":"string","description":"Instructions for the model reply when type is REPLY_INSTRUCTED_AND_HANDOVER."}}},"channels":{"type":"array","items":{"type":"string","description":"A channel an agent can serve (see AgentChannels for enablement). Also the runtime channel recorded by the channel runtimes / wilma-traces. A background task has no delivery surface and therefore no channel.","enum":["voice","chat","assistant"]},"uniqueItems":true,"description":"Channels this capability is offered on. Absent or empty — the default — means every channel the agent serves\n that can honour it. Listing channels narrows that set and never widens it: a `transfer` listed for `chat` is\n still ignored there, because the chat channel has no call to transfer."},"annotations":{"type":"array","items":{"description":"Configuration for annotations emitted after a tool completes.","oneOf":[{"type":"object","title":"tag","properties":{"tag":{"type":"object","description":"Tag annotation emitted after a tool completes.","properties":{"value":{"type":"string","description":"Static handlebars expression resolved against agent variables and helpers."},"applyOnSuccess":{"type":"boolean","description":"Apply when the tool succeeds."},"applyOnFailure":{"type":"boolean","description":"Apply when the tool fails."}}}},"required":["tag"]},{"type":"object","title":"comment","properties":{"comment":{"type":"object","description":"Comment annotation emitted after a tool completes.","properties":{"value":{"type":"string","description":"Static handlebars expression resolved against agent variables and helpers."},"applyOnSuccess":{"type":"boolean","description":"Apply when the tool succeeds."},"applyOnFailure":{"type":"boolean","description":"Apply when the tool fails."}}}},"required":["comment"]},{"type":"object","title":"variables","properties":{"variables":{"type":"object","description":"Variables annotation emitted after a tool completes."}},"required":["variables"]},{"type":"object","title":"result","properties":{"result":{"type":"object","description":"Result annotation emitted after a tool completes."}},"required":["result"]}]}}}}},"required":["handover"]},{"type":"object","title":"delegation","properties":{"delegation":{"type":"object","description":"Delegate the conversation to another agent (max 3 transitions per turn). One capability entry per delegate target.","properties":{"name":{"type":"string","description":"Tool name exposed to the model. Auto-generated when omitted."},"description":{"type":"string","description":"Description for the model to decide when to delegate."},"id":{"type":"string","maxLength":12,"minLength":12,"description":"Agent to delegate to."},"agentVersion":{"type":"number","minimum":1,"description":"Pin a specific published version of the delegate agent. When absent, the delegate's deployment configuration selects the version at delegation time."},"channels":{"type":"array","items":{"type":"string","description":"A channel an agent can serve (see AgentChannels for enablement). Also the runtime channel recorded by the channel runtimes / wilma-traces. A background task has no delivery surface and therefore no channel.","enum":["voice","chat","assistant"]},"uniqueItems":true,"description":"Channels this capability is offered on. Absent or empty — the default — means every channel the agent serves\n that can honour it. Listing channels narrows that set and never widens it: a `transfer` listed for `chat` is\n still ignored there, because the chat channel has no call to transfer."},"annotations":{"type":"array","items":{"description":"Configuration for annotations emitted after a tool completes.","oneOf":[{"type":"object","title":"tag","properties":{"tag":{"type":"object","description":"Tag annotation emitted after a tool completes.","properties":{"value":{"type":"string","description":"Static handlebars expression resolved against agent variables and helpers."},"applyOnSuccess":{"type":"boolean","description":"Apply when the tool succeeds."},"applyOnFailure":{"type":"boolean","description":"Apply when the tool fails."}}}},"required":["tag"]},{"type":"object","title":"comment","properties":{"comment":{"type":"object","description":"Comment annotation emitted after a tool completes.","properties":{"value":{"type":"string","description":"Static handlebars expression resolved against agent variables and helpers."},"applyOnSuccess":{"type":"boolean","description":"Apply when the tool succeeds."},"applyOnFailure":{"type":"boolean","description":"Apply when the tool fails."}}}},"required":["comment"]},{"type":"object","title":"variables","properties":{"variables":{"type":"object","description":"Variables annotation emitted after a tool completes."}},"required":["variables"]},{"type":"object","title":"result","properties":{"result":{"type":"object","description":"Result annotation emitted after a tool completes."}},"required":["result"]}]}}},"required":["id"]}},"required":["delegation"]},{"type":"object","title":"transfer","properties":{"transfer":{"type":"object","description":"Transfer the call to a PBX destination. Honoured on the voice channel. One capability entry per transfer target.","properties":{"name":{"type":"string","description":"Tool name exposed to the model. Auto-generated when omitted."},"description":{"type":"string","description":"Description for the model to decide when to transfer."},"variant":{"type":"string","enum":["predefined","dynamic","directory"]},"context":{"type":"string","description":"Routing context for PREDEFINED transfers."},"extension":{"type":"string","description":"Extension or phone number for PREDEFINED transfers."},"pipeline":{"type":"object","properties":{"type":{"type":"string","enum":["transfer","reply_and_transfer","reply_instructed_and_transfer"]},"instructions":{"type":"string","description":"Instructions for the model reply when type is REPLY_INSTRUCTED_AND_TRANSFER."}}},"channels":{"type":"array","items":{"type":"string","description":"A channel an agent can serve (see AgentChannels for enablement). Also the runtime channel recorded by the channel runtimes / wilma-traces. A background task has no delivery surface and therefore no channel.","enum":["voice","chat","assistant"]},"uniqueItems":true,"description":"Channels this capability is offered on. Absent or empty — the default — means every channel the agent serves\n that can honour it. Listing channels narrows that set and never widens it: a `transfer` listed for `chat` is\n still ignored there, because the chat channel has no call to transfer."},"annotations":{"type":"array","items":{"description":"Configuration for annotations emitted after a tool completes.","oneOf":[{"type":"object","title":"tag","properties":{"tag":{"type":"object","description":"Tag annotation emitted after a tool completes.","properties":{"value":{"type":"string","description":"Static handlebars expression resolved against agent variables and helpers."},"applyOnSuccess":{"type":"boolean","description":"Apply when the tool succeeds."},"applyOnFailure":{"type":"boolean","description":"Apply when the tool fails."}}}},"required":["tag"]},{"type":"object","title":"comment","properties":{"comment":{"type":"object","description":"Comment annotation emitted after a tool completes.","properties":{"value":{"type":"string","description":"Static handlebars expression resolved against agent variables and helpers."},"applyOnSuccess":{"type":"boolean","description":"Apply when the tool succeeds."},"applyOnFailure":{"type":"boolean","description":"Apply when the tool fails."}}}},"required":["comment"]},{"type":"object","title":"variables","properties":{"variables":{"type":"object","description":"Variables annotation emitted after a tool completes."}},"required":["variables"]},{"type":"object","title":"result","properties":{"result":{"type":"object","description":"Result annotation emitted after a tool completes."}},"required":["result"]}]}}}}},"required":["transfer"]},{"type":"object","title":"suggestions","properties":{"suggestions":{"type":"object","description":"Emit suggested replies for human operators. Honoured on the chat channel.","properties":{"name":{"type":"string","description":"Tool name. Auto-generated when omitted."},"instructions":{"type":"string","maxLength":2000,"minLength":1,"description":"Steers what the suggestions say. Appended to the platform's own generation prompt rather than\n replacing it, so the shape of a suggestion — short, tappable, a plausible next thing to send —\n stays the platform's to keep, and this narrows the subject: 'offer a callback and a link to the\n status page, never a discount'.\n\n NOT a tool description, unlike the `description` every other capability carries: suggestions are\n not a tool the model chooses to call. They are generated from a reply that has already been\n written, so there is no decision to describe — only the generation to steer.\n\n Absent means the platform's prompt alone."},"channels":{"type":"array","items":{"type":"string","description":"A channel an agent can serve (see AgentChannels for enablement). Also the runtime channel recorded by the channel runtimes / wilma-traces. A background task has no delivery surface and therefore no channel.","enum":["voice","chat","assistant"]},"uniqueItems":true,"description":"Channels this capability is offered on. Absent or empty — the default — means every channel the agent serves\n that can honour it. Listing channels narrows that set and never widens it: a `transfer` listed for `chat` is\n still ignored there, because the chat channel has no call to transfer."},"annotations":{"type":"array","items":{"description":"Configuration for annotations emitted after a tool completes.","oneOf":[{"type":"object","title":"tag","properties":{"tag":{"type":"object","description":"Tag annotation emitted after a tool completes.","properties":{"value":{"type":"string","description":"Static handlebars expression resolved against agent variables and helpers."},"applyOnSuccess":{"type":"boolean","description":"Apply when the tool succeeds."},"applyOnFailure":{"type":"boolean","description":"Apply when the tool fails."}}}},"required":["tag"]},{"type":"object","title":"comment","properties":{"comment":{"type":"object","description":"Comment annotation emitted after a tool completes.","properties":{"value":{"type":"string","description":"Static handlebars expression resolved against agent variables and helpers."},"applyOnSuccess":{"type":"boolean","description":"Apply when the tool succeeds."},"applyOnFailure":{"type":"boolean","description":"Apply when the tool fails."}}}},"required":["comment"]},{"type":"object","title":"variables","properties":{"variables":{"type":"object","description":"Variables annotation emitted after a tool completes."}},"required":["variables"]},{"type":"object","title":"result","properties":{"result":{"type":"object","description":"Result annotation emitted after a tool completes."}},"required":["result"]}]}}}}},"required":["suggestions"]},{"type":"object","title":"hangup","properties":{"hangup":{"type":"object","description":"Let the model end the call. Honoured on the voice channel.","properties":{"name":{"type":"string","description":"Tool name exposed to the model. Auto-generated when omitted."},"description":{"type":"string","description":"Description for the model to decide when to hang up."},"pipeline":{"type":"object","properties":{"type":{"type":"string","enum":["hangup","reply_and_hangup","reply_instructed_and_hangup"]},"instructions":{"type":"string","description":"Instructions for the model reply when type is REPLY_INSTRUCTED_AND_HANGUP."}}},"channels":{"type":"array","items":{"type":"string","description":"A channel an agent can serve (see AgentChannels for enablement). Also the runtime channel recorded by the channel runtimes / wilma-traces. A background task has no delivery surface and therefore no channel.","enum":["voice","chat","assistant"]},"uniqueItems":true,"description":"Channels this capability is offered on. Absent or empty — the default — means every channel the agent serves\n that can honour it. Listing channels narrows that set and never widens it: a `transfer` listed for `chat` is\n still ignored there, because the chat channel has no call to transfer."},"annotations":{"type":"array","items":{"description":"Configuration for annotations emitted after a tool completes.","oneOf":[{"type":"object","title":"tag","properties":{"tag":{"type":"object","description":"Tag annotation emitted after a tool completes.","properties":{"value":{"type":"string","description":"Static handlebars expression resolved against agent variables and helpers."},"applyOnSuccess":{"type":"boolean","description":"Apply when the tool succeeds."},"applyOnFailure":{"type":"boolean","description":"Apply when the tool fails."}}}},"required":["tag"]},{"type":"object","title":"comment","properties":{"comment":{"type":"object","description":"Comment annotation emitted after a tool completes.","properties":{"value":{"type":"string","description":"Static handlebars expression resolved against agent variables and helpers."},"applyOnSuccess":{"type":"boolean","description":"Apply when the tool succeeds."},"applyOnFailure":{"type":"boolean","description":"Apply when the tool fails."}}}},"required":["comment"]},{"type":"object","title":"variables","properties":{"variables":{"type":"object","description":"Variables annotation emitted after a tool completes."}},"required":["variables"]},{"type":"object","title":"result","properties":{"result":{"type":"object","description":"Result annotation emitted after a tool completes."}},"required":["result"]}]}}}}},"required":["hangup"]},{"type":"object","title":"wait","properties":{"wait":{"type":"object","description":"Let the model hold the call while it works (e.g. a lookup), keeping the caller on the line. Honoured on the voice channel.","properties":{"name":{"type":"string","description":"Tool name exposed to the model. Auto-generated when omitted."},"description":{"type":"string","description":"Description for the model to decide when to wait."},"message":{"type":"string","description":"Message spoken when entering the wait state. Spoken in every language of the call while `messages` is absent or empty; not consulted once `messages` carries any entry."},"messages":{"type":"array","items":{"type":"object","description":"One phrase in one language, for a capability that speaks a fixed line on the voice channel.\n Which entry is spoken is decided by the call's current language — the one the call resolved\n to at connect, or the one automatic detection switched to — matched on `language`\n CASE-INSENSITIVELY, the same rule `VoiceBotSpokenPhrase` states for the legacy stack, so a\n code written in either case reaches the same entry on either. Blank `text` means the agent\n says nothing in that language; it does NOT fall back to the language-agnostic member beside\n the list, which would speak one language's text in another language's voice.","properties":{"language":{"type":"string","maxLength":8,"minLength":2,"pattern":"^[a-z]{2,3}(-[A-Z]{2})?$","description":"BCP-47 language tag, optionally with a region subtag: 'en', 'en-US', 'pt-BR'. Deliberately a\n constrained string rather than an enum: the offerable set is a property of the selected\n recogniser and changes with it, so an enumeration here would cap the set at whatever was known\n when the model was released."},"text":{"type":"string"}},"required":["language","text"]},"description":"Per-language wait messages. Same rule as `AgentTool.startMessages`: the entry for the call's current language is spoken, a language with no entry is silent, and with no effective speech configuration `message` is spoken."},"channels":{"type":"array","items":{"type":"string","description":"A channel an agent can serve (see AgentChannels for enablement). Also the runtime channel recorded by the channel runtimes / wilma-traces. A background task has no delivery surface and therefore no channel.","enum":["voice","chat","assistant"]},"uniqueItems":true,"description":"Channels this capability is offered on. Absent or empty — the default — means every channel the agent serves\n that can honour it. Listing channels narrows that set and never widens it: a `transfer` listed for `chat` is\n still ignored there, because the chat channel has no call to transfer."},"annotations":{"type":"array","items":{"description":"Configuration for annotations emitted after a tool completes.","oneOf":[{"type":"object","title":"tag","properties":{"tag":{"type":"object","description":"Tag annotation emitted after a tool completes.","properties":{"value":{"type":"string","description":"Static handlebars expression resolved against agent variables and helpers."},"applyOnSuccess":{"type":"boolean","description":"Apply when the tool succeeds."},"applyOnFailure":{"type":"boolean","description":"Apply when the tool fails."}}}},"required":["tag"]},{"type":"object","title":"comment","properties":{"comment":{"type":"object","description":"Comment annotation emitted after a tool completes.","properties":{"value":{"type":"string","description":"Static handlebars expression resolved against agent variables and helpers."},"applyOnSuccess":{"type":"boolean","description":"Apply when the tool succeeds."},"applyOnFailure":{"type":"boolean","description":"Apply when the tool fails."}}}},"required":["comment"]},{"type":"object","title":"variables","properties":{"variables":{"type":"object","description":"Variables annotation emitted after a tool completes."}},"required":["variables"]},{"type":"object","title":"result","properties":{"result":{"type":"object","description":"Result annotation emitted after a tool completes."}},"required":["result"]}]}}}}},"required":["wait"]}]},"description":"Everything the agent can do. When a `workflow` is set these are the capabilities in scope at every step, and a step may add to them or narrow them with its own list of the same shape."},"variables":{"type":"array","items":{"type":"object","properties":{"name":{"type":"string","maxLength":128,"minLength":1,"pattern":"^[a-zA-Z0-9_]+$"},"type":{"type":"string","enum":["string","number","boolean","string_array","number_array"]},"optional":{"type":"boolean"},"description":{"type":"string","description":"What the variable means and how it should be filled."}},"required":["name","optional","type"]}}},"required":["instructions"]}},"required":["ai"]},{"type":"object","title":"webhook","properties":{"webhook":{"type":"object","description":"Bring-your-own backend over HTTPS. The platform POSTs call events to the URL; the backend\nanswers out of band through the channel's command API. The response body is not read.","properties":{"url":{"type":"string"},"secret":{"type":"object","description":"A reference to a stored secret, held by other services in place of the value.\nResolved server-to-server via GetSecretValue at execution time — always to the current value.","properties":{"id":{"type":"string","maxLength":8,"minLength":8,"pattern":"^[0-9A-Za-z]{8}$","description":"Unique identifier of a secret."}},"required":["id"]},"timeout":{"type":"number","default":10000,"maximum":120000,"minimum":5000,"description":"Timeout of one delivery, in milliseconds."}},"required":["url"]}},"required":["webhook"]},{"type":"object","title":"sqs","properties":{"sqs":{"type":"object","description":"Bring-your-own backend over SQS. The platform sends call events to the queue; the backend\nanswers out of band through the channel's command API.","properties":{"url":{"type":"string"},"key":{"type":"string"},"secret":{"type":"object","description":"A reference to a stored secret, held by other services in place of the value.\nResolved server-to-server via GetSecretValue at execution time — always to the current value.","properties":{"id":{"type":"string","maxLength":8,"minLength":8,"pattern":"^[0-9A-Za-z]{8}$","description":"Unique identifier of a secret."}},"required":["id"]}},"required":["key","url"]}},"required":["sqs"]}]},"settings":{"type":"object","description":"Agent-level settings shared across all channels.","properties":{"pbxGroupId":{"type":"string","description":"PBX group used for data ACL (e.g. Sales Intelligence lookups)."}}},"access":{"type":"object","description":"Who is allowed to use the agent. There is no view/edit dimension — access solely grants the ability to use the agent.\n\n Where it is applied, per channel:\n - `assistant` — enforced. GetAgent on wilma-assistant refuses an agent the caller is not granted.\n - `voice` — deliberately not applied. Inbound calls are routed by the PBX dialplan, and the data ACL is the\n agent-level `settings.pbxGroupId`, not per-user grants.\n - `chat` — enforced for COLLEAGUES ONLY. wilma-chat checks the sender against this block when the sender\n belongs to the agent's own company. A sender from outside it — a Kite website visitor, an SMS or WhatsApp\n contact — is not governed by this field at all and reaches the agent under every visibility; what exposes\n an agent to them is an administrator wiring it to that surface. A refused colleague receives one short\n refusal message and no turn is started.\n A `groupId` grant is weaker here than on the assistant: this channel reads the sender's group from their\n x-bees user record, which does not always carry one. When EVERY grant on the agent is a `groupId` grant and\n the sender's group is unknown, the sender is PASSED rather than refused — refusing would lock out everyone\n whose record lacks a group, which is a large minority. Adding a `userId` or `department` grant removes that\n waiver and the agent is enforced normally, so use one of those where the restriction must hold on chat.\n (x-bees chat bots are a separate stack with their own company-level `BotAccess`; it is not this field.)\n\n When an agent is invoked as a background task its agent-level access is not used — invocation access is configured\n per reference on the calling agent (see AgentCallCapability.access).","properties":{"visibility":{"type":"string","enum":["explicit","organization","public"]},"grants":{"type":"array","items":{"description":"A user, group or department that access is granted to.","oneOf":[{"type":"object","title":"userId","properties":{"userId":{"type":"string","maxLength":12,"minLength":12}},"required":["userId"]},{"type":"object","title":"groupId","properties":{"groupId":{"type":"string"}},"required":["groupId"]},{"type":"object","title":"department","properties":{"department":{"type":"string","description":"PBX department NAME, as carried on the colleague record (`PbxColleague.department`), not a department id.\n The x-bees directory knows a colleague's department only by name, so matching by name is what lets the check\n read a field the runtime already has instead of resolving the PBX department tree on every request.\n Two consequences: renaming a department in the PBX invalidates every grant naming it, and two departments\n with the same name in different branches of the tree are one value here."}},"required":["department"]}]},"description":"Users, groups and departments granted the ability to use the agent. Applies when visibility is `explicit`."}}},"revision":{"type":"number","description":"Draft revision this update is based on (optimistic locking). When it does not match the current revision, RevisionConflictException is returned."}},"required":["channels","engine","name","revision"]}}},"delete":{"operationId":"DeleteAgent","summary":"","description":"Deletes an agent, its draft, versions and API keys. Fails with ConflictException while the agent has active sessions or is referenced by other agents.\n\n### Allowed Auth Scopes\n\n- wilma-agents:DeleteAgent","parameters":[{"name":"agentId","in":"path","required":true,"schema":{"type":"string","maxLength":12,"minLength":12}},{"name":"company","in":"query","required":false,"schema":{"type":"string","maxLength":30,"minLength":3}}]}},"/v2/agents/agents/{agentId}/compare-versions":{"get":{"operationId":"CompareAgentVersions","summary":"","description":"Returns the field-level differences between two versions, or between a version and the current draft.\n\n### Allowed Auth Scopes\n\n- wilma-agents:CompareAgentVersions\n- wilma-agents:read","parameters":[{"name":"agentId","in":"path","required":true,"schema":{"type":"string","maxLength":12,"minLength":12}},{"name":"company","in":"query","required":false,"schema":{"type":"string","maxLength":30,"minLength":3}},{"name":"from","in":"query","required":false,"schema":{"type":"number","minimum":1,"description":"Base published version number. Omit when fromDraft is true."},"description":"Base published version number. Omit when fromDraft is true."},{"name":"fromDraft","in":"query","required":false,"schema":{"type":"boolean","description":"Use the current draft as the base instead of a published version."},"description":"Use the current draft as the base instead of a published version."},{"name":"to","in":"query","required":false,"schema":{"type":"number","minimum":1,"description":"Target published version number. Omit when toDraft is true."},"description":"Target published version number. Omit when toDraft is true."},{"name":"toDraft","in":"query","required":false,"schema":{"type":"boolean","description":"Use the current draft as the target instead of a published version."},"description":"Use the current draft as the target instead of a published version."}]}},"/v2/agents/agents/{agentId}/deployment":{"get":{"operationId":"GetAgentDeployment","summary":"","description":"Retrieves how new sessions are currently split across the agent's published versions.\n\n### Allowed Auth Scopes\n\n- wilma-agents:GetAgentDeployment\n- wilma-agents:read","parameters":[{"name":"agentId","in":"path","required":true,"schema":{"type":"string","maxLength":12,"minLength":12}},{"name":"company","in":"query","required":false,"schema":{"type":"string","maxLength":30,"minLength":3}}]},"put":{"operationId":"PutAgentDeployment","summary":"","description":"Sets how new sessions are split across the agent's published versions, e.g. 80% on version 1 and 20% on version 2. Percentages must total 100. Running sessions keep the version they started on.\n\n### Allowed Auth Scopes\n\n- wilma-agents:PutAgentDeployment","parameters":[{"name":"agentId","in":"path","required":true,"schema":{"type":"string","maxLength":12,"minLength":12}},{"name":"company","in":"query","required":false,"schema":{"type":"string","maxLength":30,"minLength":3}}],"requestBody":{"required":true,"schema":{"type":"object","properties":{"deployment":{"type":"array","items":{"type":"object","properties":{"version":{"type":"number","minimum":1,"description":"Published agent version number. Monotonic within an agent's version history, starting at 1."},"percentage":{"type":"number","maximum":100,"minimum":1,"description":"Percentage of new sessions routed to this version. Percentages must sum to 100."}},"required":["percentage","version"]},"maxItems":10,"minItems":1,"description":"Weighted routing over distinct published versions. Percentages must sum to 100; a single entry at 100 serves one version to everyone."}},"required":["deployment"]}}}},"/v2/agents/agents/{agentId}/deployment/reset":{"post":{"operationId":"ResetAgentDeployment","summary":"","description":"Resets the deployment to the default: 100% of new sessions on the latest published version. Fails with ConflictException while the agent has no published version.\n\n### Allowed Auth Scopes\n\n- wilma-agents:ResetAgentDeployment","parameters":[{"name":"agentId","in":"path","required":true,"schema":{"type":"string","maxLength":12,"minLength":12}},{"name":"company","in":"query","required":false,"schema":{"type":"string","maxLength":30,"minLength":3}}]}},"/v2/agents/agents/{agentId}/keys":{"get":{"operationId":"ListAgentApiKeys","summary":"","description":"Lists the API keys of an agent. Secrets are never returned.\n\n### Allowed Auth Scopes\n\n- wilma-agents:ListAgentApiKeys\n- wilma-agents:read","parameters":[{"name":"agentId","in":"path","required":true,"schema":{"type":"string","maxLength":12,"minLength":12}},{"name":"company","in":"query","required":false,"schema":{"type":"string","maxLength":30,"minLength":3}}]},"post":{"operationId":"CreateAgentApiKey","summary":"","description":"Creates a new API key for an agent. The secret is returned only in this response and cannot be retrieved later.\n\n### Allowed Auth Scopes\n\n- wilma-agents:CreateAgentApiKey","parameters":[{"name":"agentId","in":"path","required":true,"schema":{"type":"string","maxLength":12,"minLength":12}},{"name":"company","in":"query","required":false,"schema":{"type":"string","maxLength":30,"minLength":3}}],"requestBody":{"required":false,"schema":{"type":"object","properties":{"name":{"type":"string","maxLength":64,"minLength":1},"expiresAt":{"type":"string","description":"Optional expiry; the key stops working after this time."}}}}}},"/v2/agents/agents/{agentId}/keys/{keyId}":{"delete":{"operationId":"DeleteAgentApiKey","summary":"","description":"Revokes an API key.\n\n### Allowed Auth Scopes\n\n- wilma-agents:DeleteAgentApiKey","parameters":[{"name":"agentId","in":"path","required":true,"schema":{"type":"string","maxLength":12,"minLength":12}},{"name":"keyId","in":"path","required":true,"schema":{"type":"string","maxLength":12,"minLength":12}},{"name":"company","in":"query","required":false,"schema":{"type":"string","maxLength":30,"minLength":3}}]}},"/v2/agents/agents/{agentId}/versions":{"get":{"operationId":"ListAgentVersions","summary":"","description":"Lists the published versions of an agent, newest first.\n\n### Allowed Auth Scopes\n\n- wilma-agents:ListAgentVersions\n- wilma-agents:read","parameters":[{"name":"agentId","in":"path","required":true,"schema":{"type":"string","maxLength":12,"minLength":12}},{"name":"company","in":"query","required":false,"schema":{"type":"string","maxLength":30,"minLength":3}},{"name":"limit","in":"query","required":false,"schema":{"type":"number","maximum":100,"minimum":1}},{"name":"cursor","in":"query","required":false,"schema":{"type":"string","maxLength":2048,"minLength":1,"description":"Opaque cursor returned by a previous page; pass it back to fetch the next page."},"description":"Opaque cursor returned by a previous page; pass it back to fetch the next page."}]},"post":{"operationId":"PublishAgentVersion","summary":"","description":"Publishes the current draft as a new immutable version. Publishing never changes what is served; call PutAgentDeployment separately to route sessions to it.\n\n### Allowed Auth Scopes\n\n- wilma-agents:PublishAgentVersion","parameters":[{"name":"agentId","in":"path","required":true,"schema":{"type":"string","maxLength":12,"minLength":12}},{"name":"company","in":"query","required":false,"schema":{"type":"string","maxLength":30,"minLength":3}}],"requestBody":{"required":true,"schema":{"type":"object","properties":{"changelog":{"type":"string","maxLength":500,"minLength":1,"description":"What changed in this version."},"name":{"type":"string","maxLength":255,"minLength":1,"description":"Optional human-readable name for this version."},"revision":{"type":"number","description":"Draft revision this publish is based on (optimistic locking)."}},"required":["revision"]}}}},"/v2/agents/agents/{agentId}/versions/{version}":{"get":{"operationId":"GetAgentVersion","summary":"","description":"Retrieves a published version, including its full immutable definition snapshot.\n\n### Allowed Auth Scopes\n\n- wilma-agents:GetAgentVersion\n- wilma-agents:read","parameters":[{"name":"agentId","in":"path","required":true,"schema":{"type":"string","maxLength":12,"minLength":12}},{"name":"version","in":"path","required":true,"schema":{"type":"number","minimum":1,"description":"Published agent version number. Monotonic within an agent's version history, starting at 1."},"description":"Published agent version number. Monotonic within an agent's version history, starting at 1."},{"name":"company","in":"query","required":false,"schema":{"type":"string","maxLength":30,"minLength":3}}]}},"/v2/agents/agents/{agentId}/versions/{version}/draft":{"post":{"operationId":"RestoreAgentVersionToDraft","summary":"","description":"Replaces the mutable draft with a published version's definition. The draft revision is incremented; published versions and the deployment configuration are unchanged.\n\n### Allowed Auth Scopes\n\n- wilma-agents:RestoreAgentVersionToDraft","parameters":[{"name":"agentId","in":"path","required":true,"schema":{"type":"string","maxLength":12,"minLength":12}},{"name":"version","in":"path","required":true,"schema":{"type":"number","minimum":1,"description":"Published agent version number. Monotonic within an agent's version history, starting at 1."},"description":"Published agent version number. Monotonic within an agent's version history, starting at 1."},{"name":"company","in":"query","required":false,"schema":{"type":"string","maxLength":30,"minLength":3}}],"requestBody":{"required":true,"schema":{"type":"object","properties":{"revision":{"type":"number","description":"Draft revision this restore is based on (optimistic locking)."}},"required":["revision"]}}}},"/v2/artifacts":{"get":{"operationId":"ListArtifacts","summary":"","description":"Lists artifacts. Listing is contextual: filter by the producing session or task (and optionally channel, agent or kind). There is no global company-wide browser — discovery of artifacts outside their producing context happens via shared links.\n\n### Allowed Auth Scopes\n\n- wilma-artifacts:ListArtifacts\n- wilma-artifacts:read","parameters":[{"name":"company","in":"query","required":false,"schema":{"type":"string","maxLength":30,"minLength":3,"description":"The unique identifier of the tenant when a service token is used."},"description":"The unique identifier of the tenant when a service token is used."},{"name":"user","in":"query","required":false,"schema":{"type":"string","maxLength":12,"minLength":12,"description":"The user the artifacts belong to. Optional: identifies the user when a service or tenant token is used."},"description":"The user the artifacts belong to. Optional: identifies the user when a service or tenant token is used."},{"name":"sessionId","in":"query","required":false,"schema":{"type":"string","maxLength":128,"minLength":1,"description":"Only artifacts produced by this session."},"description":"Only artifacts produced by this session."},{"name":"taskId","in":"query","required":false,"schema":{"type":"string","description":"Only artifacts produced by this background task."},"description":"Only artifacts produced by this background task."},{"name":"channel","in":"query","required":false,"schema":{"type":"string","description":"A channel an agent can serve (see AgentChannels for enablement). Also the runtime channel recorded by the channel runtimes / wilma-traces. A background task has no delivery surface and therefore no channel.","enum":["voice","chat","assistant"]},"description":"Only artifacts produced on this channel. Never matches task-produced artifacts, which carry no channel."},{"name":"agentId","in":"query","required":false,"schema":{"type":"string","maxLength":12,"minLength":12,"description":"Only artifacts produced by this agent."},"description":"Only artifacts produced by this agent."},{"name":"kind","in":"query","required":false,"schema":{"type":"string","description":"Semantic classification driving the preview. Each revision is one CDS file; an archive may also carry a bundle manifest.","enum":["document","code","html","svg","diagram","component","file","bundle"]},"description":"Semantic classification driving the preview. Each revision is one CDS file; an archive may also carry a bundle manifest."},{"name":"from","in":"query","required":false,"schema":{"type":"string","description":"Only artifacts created at or after this time."},"description":"Only artifacts created at or after this time."},{"name":"to","in":"query","required":false,"schema":{"type":"string","description":"Only artifacts created before this time."},"description":"Only artifacts created before this time."},{"name":"limit","in":"query","required":false,"schema":{"type":"number","maximum":100,"minimum":1}},{"name":"cursor","in":"query","required":false,"schema":{"type":"string","maxLength":2048,"minLength":1,"description":"Opaque cursor returned by a previous page; pass it back to fetch the next page."},"description":"Opaque cursor returned by a previous page; pass it back to fetch the next page."}]},"post":{"operationId":"CreateArtifact","summary":"","description":"Registers a new artifact and returns a short-lived presigned URL to upload its content.\n Runtime-facing: called by the channel runtimes (assistant, chat, workflows) when an agent\n shares an artifact, not by end clients. The artifact becomes downloadable once the upload\n completes.\n\n### Allowed Auth Scopes\n\n- wilma-artifacts:CreateArtifact","parameters":[{"name":"company","in":"query","required":false,"schema":{"type":"string","maxLength":30,"minLength":3,"description":"The unique identifier of the tenant when a service token is used."},"description":"The unique identifier of the tenant when a service token is used."},{"name":"user","in":"query","required":false,"schema":{"type":"string","maxLength":12,"minLength":12,"description":"The user the artifact belongs to (the owner). Optional: identifies the user when a service or tenant token is used."},"description":"The user the artifact belongs to (the owner). Optional: identifies the user when a service or tenant token is used."}],"requestBody":{"required":true,"schema":{"type":"object","properties":{"title":{"type":"string","description":"Human-readable title shown in the UI."},"name":{"type":"string","maxLength":1024,"minLength":1,"description":"Download filename, e.g. 'report.md'."},"kind":{"type":"string","description":"Semantic classification driving the preview. Each revision is one CDS file; an archive may also carry a bundle manifest.","enum":["document","code","html","svg","diagram","component","file","bundle"]},"contentType":{"type":"string","description":"MIME type of the content, e.g. 'text/markdown'."},"size":{"type":"number","description":"Size in bytes of the content that will be uploaded."},"source":{"type":"object","description":"Provenance of an artifact: the runtime, session or task, and agent that produced it. Exactly one reference is present: a channel session sets `channel` and `sessionId`, a background task sets `taskId` and no channel, since a task has no delivery surface.","properties":{"channel":{"type":"string","description":"A channel an agent can serve (see AgentChannels for enablement). Also the runtime channel recorded by the channel runtimes / wilma-traces. A background task has no delivery surface and therefore no channel.","enum":["voice","chat","assistant"]},"sessionId":{"type":"string","maxLength":128,"minLength":1,"description":"Session that produced the artifact (assistant / chat channels)."},"taskId":{"type":"string","description":"Background task (wilma-workflows) that produced the artifact."},"agentId":{"type":"string","maxLength":12,"minLength":12,"description":"Agent that produced the artifact."},"agentVersion":{"type":"number","minimum":1,"description":"Published agent version that produced the artifact; absent for draft runs."}}}},"required":["kind","source","title"]}}}},"/v2/artifacts/items/{artifactId}":{"get":{"operationId":"GetArtifact","summary":"","description":"Gets the metadata of a single artifact revision. Accessible to the owner, granted principals, and — when visibility is `company` — any authenticated user of the same company.\n\n### Allowed Auth Scopes\n\n- wilma-artifacts:GetArtifact\n- wilma-artifacts:read","parameters":[{"name":"artifactId","in":"path","required":true,"schema":{"type":"string","maxLength":12,"minLength":12,"pattern":"^[A-Za-z0-9_-]{12}$","description":"Nano-id artifact identifier (12 characters), unguessable by design."},"description":"Nano-id artifact identifier (12 characters), unguessable by design."},{"name":"company","in":"query","required":false,"schema":{"type":"string","maxLength":30,"minLength":3,"description":"The unique identifier of the tenant when a service token is used."},"description":"The unique identifier of the tenant when a service token is used."},{"name":"user","in":"query","required":false,"schema":{"type":"string","maxLength":12,"minLength":12,"description":"The user the artifact belongs to. Optional: identifies the user when a service or tenant token is used."},"description":"The user the artifact belongs to. Optional: identifies the user when a service or tenant token is used."},{"name":"revision","in":"query","required":false,"schema":{"type":"number","minimum":1,"description":"Revision to read. Omit for the latest revision. Earlier uploads remain available under the artifact's current access settings."},"description":"Revision to read. Omit for the latest revision. Earlier uploads remain available under the artifact's current access settings."}]},"delete":{"operationId":"DeleteArtifact","summary":"","description":"Permanently deletes an artifact, all retained revisions and their CDS files, and invalidates any share link. Terminal and not reversible.\n\n### Allowed Auth Scopes\n\n- wilma-artifacts:DeleteArtifact","parameters":[{"name":"artifactId","in":"path","required":true,"schema":{"type":"string","maxLength":12,"minLength":12,"pattern":"^[A-Za-z0-9_-]{12}$","description":"Nano-id artifact identifier (12 characters), unguessable by design."},"description":"Nano-id artifact identifier (12 characters), unguessable by design."},{"name":"company","in":"query","required":false,"schema":{"type":"string","maxLength":30,"minLength":3,"description":"The unique identifier of the tenant when a service token is used."},"description":"The unique identifier of the tenant when a service token is used."},{"name":"user","in":"query","required":false,"schema":{"type":"string","maxLength":12,"minLength":12,"description":"The user the artifact belongs to. Optional: identifies the user when a service or tenant token is used."},"description":"The user the artifact belongs to. Optional: identifies the user when a service or tenant token is used."}]}},"/v2/artifacts/items/{artifactId}/content-url":{"get":{"operationId":"GetArtifactContentUrl","summary":"","description":"Returns a short-lived URL that serves an artifact's content for rendering. The entry file sits\n under a signed prefix that every other file of the artifact also resolves against, so a site's\n relative links, stylesheets, scripts and images load exactly as they would from a web server:\n `baseUrl` + a file's `path` serves that file. The URL needs no authorization header, so it can\n be an iframe `src`, an `img` `src` or a plain `fetch`. HTML, SVG and XML are served with a\n sandboxing Content-Security-Policy (an opaque origin, no storage, no cookies, network limited to\n the artifact's own files and a few public CDNs), which holds even if the URL is opened directly;\n render such content in an iframe, never inline in the host page. Same access rules as GetArtifact.\n\n### Allowed Auth Scopes\n\n- wilma-artifacts:GetArtifactContentUrl\n- wilma-artifacts:read","parameters":[{"name":"artifactId","in":"path","required":true,"schema":{"type":"string","maxLength":12,"minLength":12,"pattern":"^[A-Za-z0-9_-]{12}$","description":"Nano-id artifact identifier (12 characters), unguessable by design."},"description":"Nano-id artifact identifier (12 characters), unguessable by design."},{"name":"company","in":"query","required":false,"schema":{"type":"string","maxLength":30,"minLength":3,"description":"The unique identifier of the tenant when a service token is used."},"description":"The unique identifier of the tenant when a service token is used."},{"name":"user","in":"query","required":false,"schema":{"type":"string","maxLength":12,"minLength":12,"description":"The user the artifact belongs to. Optional: identifies the user when a service or tenant token is used."},"description":"The user the artifact belongs to. Optional: identifies the user when a service or tenant token is used."},{"name":"revision","in":"query","required":false,"schema":{"type":"number","minimum":1,"description":"Revision to read. Omit for the latest revision. Earlier uploads remain available under the artifact's current access settings."},"description":"Revision to read. Omit for the latest revision. Earlier uploads remain available under the artifact's current access settings."}]}},"/v2/artifacts/items/{artifactId}/download":{"get":{"operationId":"DownloadArtifact","summary":"","description":"Returns a short-lived URL to download an artifact's content: the file itself for a single-file artifact, the stored ZIP for a bundle. Same access rules as GetArtifact.\n\n### Allowed Auth Scopes\n\n- wilma-artifacts:DownloadArtifact\n- wilma-artifacts:read","parameters":[{"name":"artifactId","in":"path","required":true,"schema":{"type":"string","maxLength":12,"minLength":12,"pattern":"^[A-Za-z0-9_-]{12}$","description":"Nano-id artifact identifier (12 characters), unguessable by design."},"description":"Nano-id artifact identifier (12 characters), unguessable by design."},{"name":"company","in":"query","required":false,"schema":{"type":"string","maxLength":30,"minLength":3,"description":"The unique identifier of the tenant when a service token is used."},"description":"The unique identifier of the tenant when a service token is used."},{"name":"user","in":"query","required":false,"schema":{"type":"string","maxLength":12,"minLength":12,"description":"The user the artifact belongs to. Optional: identifies the user when a service or tenant token is used."},"description":"The user the artifact belongs to. Optional: identifies the user when a service or tenant token is used."},{"name":"revision","in":"query","required":false,"schema":{"type":"number","minimum":1,"description":"Revision to read. Omit for the latest revision. Earlier uploads remain available under the artifact's current access settings."},"description":"Revision to read. Omit for the latest revision. Earlier uploads remain available under the artifact's current access settings."}]}},"/v2/artifacts/items/{artifactId}/share-url":{"get":{"operationId":"GetArtifactShareUrl","summary":"","description":"Returns the shareable URL of an artifact whose visibility is `company` using the configured viewer base and artifact id. The URL points to the authenticated artifact viewer and works outside any session context; fails with ConflictException when the artifact is private.\n\n### Allowed Auth Scopes\n\n- wilma-artifacts:GetArtifactShareUrl\n- wilma-artifacts:read","parameters":[{"name":"artifactId","in":"path","required":true,"schema":{"type":"string","maxLength":12,"minLength":12,"pattern":"^[A-Za-z0-9_-]{12}$","description":"Nano-id artifact identifier (12 characters), unguessable by design."},"description":"Nano-id artifact identifier (12 characters), unguessable by design."},{"name":"company","in":"query","required":false,"schema":{"type":"string","maxLength":30,"minLength":3,"description":"The unique identifier of the tenant when a service token is used."},"description":"The unique identifier of the tenant when a service token is used."},{"name":"user","in":"query","required":false,"schema":{"type":"string","maxLength":12,"minLength":12,"description":"The user the artifact belongs to. Optional: identifies the user when a service or tenant token is used."},"description":"The user the artifact belongs to. Optional: identifies the user when a service or tenant token is used."}]}},"/v2/artifacts/items/{artifactId}/sharing":{"put":{"operationId":"UpdateArtifactSharing","summary":"","description":"Sets an artifact's sharing settings: specific per-user/group grants plus a general access\n level — `private` (owner + granted users only) or `company` (anyone in the owner's company\n with the link, sign-in required). The viewer URL is stable across sharing changes. Moving\n back to `private` removes company-wide access; the owner and granted users retain access.\n Already-issued content and download URLs remain usable until their expiry.\n\n### Allowed Auth Scopes\n\n- wilma-artifacts:UpdateArtifactSharing","parameters":[{"name":"artifactId","in":"path","required":true,"schema":{"type":"string","maxLength":12,"minLength":12,"pattern":"^[A-Za-z0-9_-]{12}$","description":"Nano-id artifact identifier (12 characters), unguessable by design."},"description":"Nano-id artifact identifier (12 characters), unguessable by design."},{"name":"company","in":"query","required":false,"schema":{"type":"string","maxLength":30,"minLength":3,"description":"The unique identifier of the tenant when a service token is used."},"description":"The unique identifier of the tenant when a service token is used."},{"name":"user","in":"query","required":false,"schema":{"type":"string","maxLength":12,"minLength":12,"description":"The user the artifact belongs to. Optional: identifies the user when a service or tenant token is used."},"description":"The user the artifact belongs to. Optional: identifies the user when a service or tenant token is used."}],"requestBody":{"required":true,"schema":{"type":"object","properties":{"access":{"type":"object","description":"Sharing settings for an artifact: specific per-user/group grants (\"people with access\")\n plus a general access level. Shared access is view-only.","properties":{"visibility":{"type":"string","description":"General access level of an artifact, applied on top of any specific-user grants. Shared\n access is view-only. Artifacts are company-internal: there is no public level, and every\n access requires authentication. Moving back to `private` removes company-wide access; the same viewer link still\n opens for the owner and granted users. Sharing can be enabled again.","enum":["private","company"]},"grants":{"type":"array","items":{"description":"A user or group granted direct access to an artifact.","oneOf":[{"type":"object","title":"userId","properties":{"userId":{"type":"string","maxLength":12,"minLength":12}},"required":["userId"]},{"type":"object","title":"groupId","properties":{"groupId":{"type":"string"}},"required":["groupId"]}]},"description":"Specific users or groups granted direct view access, independent of `visibility`. The owner always has access."}},"required":["visibility"]},"notify":{"type":"object","description":"People to notify when sharing changes, with an optional message.","properties":{"emails":{"type":"array","items":{"type":"string"}},"message":{"type":"string"}},"required":["emails"]}},"required":["access"]}}}},"/v2/assistant/agents/items/{agentId}":{"get":{"operationId":"assistant.GetAgent","summary":"","description":"Gets the end-user view of one agent: its name, picture and the assistant presentation a client draws\n before the first turn (welcome message, quick commands, composer placeholder). This is the only agent\n read an end user needs — instructions, capabilities and access rules stay in wilma-agents, which serves\n the administrator.\n\n Authorization is optional, and what it changes is reach rather than shape. An authenticated caller\n resolves any agent they are granted. An anonymous caller resolves only an agent whose access visibility\n is `public`, and must name the tenant with `company` since the request carries none. Anything else\n answers NotFound rather than Forbidden, so an anonymous probe cannot use this operation to discover\n which agent ids exist.\n\n### Allowed Auth Scopes\n\n- wilma-assistant:GetAgent\n- wilma-assistant:read","parameters":[{"name":"agentId","in":"path","required":true,"schema":{"type":"string","maxLength":12,"minLength":12}},{"name":"company","in":"query","required":false,"schema":{"type":"string","maxLength":30,"minLength":3,"description":"The tenant the agent belongs to. Required when the call is anonymous, since there is no token to read it from; otherwise the unique identifier of the tenant when a service token is used."},"description":"The tenant the agent belongs to. Required when the call is anonymous, since there is no token to read it from; otherwise the unique identifier of the tenant when a service token is used."},{"name":"user","in":"query","required":false,"schema":{"type":"string","maxLength":12,"minLength":12,"description":"The unique identifier of the user when a service or tenant token is used. Ignored on anonymous calls."},"description":"The unique identifier of the user when a service or tenant token is used. Ignored on anonymous calls."}]}},"/v2/assistant/attachments":{"post":{"operationId":"UploadAttachment","summary":"","description":"Registers an input attachment and returns a short-lived presigned PUT URL for its content.\n Upload the bytes to the URL, then pass the returned `attachmentId` in the `attachments` list\n of StartSession or ContinueSession. Attachments carry no session reference and can be uploaded\n before a session exists; content type, size and preview are recorded from the uploaded bytes,\n and the content becomes downloadable once the PUT completes.\n\n### Allowed Auth Scopes\n\n- wilma-assistant:UploadAttachment","parameters":[{"name":"company","in":"query","required":false,"schema":{"type":"string","maxLength":30,"minLength":3,"description":"The unique identifier of the tenant when a service token is used."},"description":"The unique identifier of the tenant when a service token is used."},{"name":"user","in":"query","required":false,"schema":{"type":"string","maxLength":12,"minLength":12,"description":"The user the attachment belongs to. Optional: identifies the user when a service or tenant token is used; when omitted the request is not scoped to a specific user."},"description":"The user the attachment belongs to. Optional: identifies the user when a service or tenant token is used; when omitted the request is not scoped to a specific user."}],"requestBody":{"required":true,"schema":{"type":"object","properties":{"name":{"type":"string","maxLength":255,"minLength":1,"description":"Filename shown to the user, e.g. 'quarterly-report.pdf'."}},"required":["name"]}}}},"/v2/assistant/attachments/items/{attachmentId}":{"get":{"operationId":"GetAttachment","summary":"","description":"Gets the metadata of a single input attachment.\n\n### Allowed Auth Scopes\n\n- wilma-assistant:GetAttachment\n- wilma-assistant:read","parameters":[{"name":"attachmentId","in":"path","required":true,"schema":{"type":"string","maxLength":12,"minLength":12,"pattern":"^[A-Za-z0-9_-]{12}$","description":"Nano-id attachment identifier (12 characters)."},"description":"Nano-id attachment identifier (12 characters)."},{"name":"company","in":"query","required":false,"schema":{"type":"string","maxLength":30,"minLength":3,"description":"The unique identifier of the tenant when a service token is used."},"description":"The unique identifier of the tenant when a service token is used."},{"name":"user","in":"query","required":false,"schema":{"type":"string","maxLength":12,"minLength":12,"description":"The user the attachment belongs to. Optional: identifies the user when a service or tenant token is used; when omitted the request is not scoped to a specific user."},"description":"The user the attachment belongs to. Optional: identifies the user when a service or tenant token is used; when omitted the request is not scoped to a specific user."}]},"delete":{"operationId":"DeleteAttachment","summary":"","description":"Deletes an attachment and its content.\n\n### Allowed Auth Scopes\n\n- wilma-assistant:DeleteAttachment","parameters":[{"name":"attachmentId","in":"path","required":true,"schema":{"type":"string","maxLength":12,"minLength":12,"pattern":"^[A-Za-z0-9_-]{12}$","description":"Nano-id attachment identifier (12 characters)."},"description":"Nano-id attachment identifier (12 characters)."},{"name":"company","in":"query","required":false,"schema":{"type":"string","maxLength":30,"minLength":3,"description":"The unique identifier of the tenant when a service token is used."},"description":"The unique identifier of the tenant when a service token is used."},{"name":"user","in":"query","required":false,"schema":{"type":"string","maxLength":12,"minLength":12,"description":"The user the attachment belongs to. Optional: identifies the user when a service or tenant token is used; when omitted the request is not scoped to a specific user."},"description":"The user the attachment belongs to. Optional: identifies the user when a service or tenant token is used; when omitted the request is not scoped to a specific user."}]}},"/v2/assistant/attachments/items/{attachmentId}/download":{"get":{"operationId":"DownloadAttachment","summary":"","description":"Returns a short-lived presigned URL to download an attachment's content.\n\n### Allowed Auth Scopes\n\n- wilma-assistant:DownloadAttachment\n- wilma-assistant:read","parameters":[{"name":"attachmentId","in":"path","required":true,"schema":{"type":"string","maxLength":12,"minLength":12,"pattern":"^[A-Za-z0-9_-]{12}$","description":"Nano-id attachment identifier (12 characters)."},"description":"Nano-id attachment identifier (12 characters)."},{"name":"company","in":"query","required":false,"schema":{"type":"string","maxLength":30,"minLength":3,"description":"The unique identifier of the tenant when a service token is used."},"description":"The unique identifier of the tenant when a service token is used."},{"name":"user","in":"query","required":false,"schema":{"type":"string","maxLength":12,"minLength":12,"description":"The user the attachment belongs to. Optional: identifies the user when a service or tenant token is used; when omitted the request is not scoped to a specific user."},"description":"The user the attachment belongs to. Optional: identifies the user when a service or tenant token is used; when omitted the request is not scoped to a specific user."}]}},"/v2/assistant/connectors":{"get":{"operationId":"ListConnectors","summary":"","description":"Lists the connectors the current user can use in the assistant: every instance the company\n configured whose definition this deployment can execute, one entry per instance. Each entry\n says whether the connector needs the user's own authorization, whether the user has\n connected it yet, and whether it is enabled for them. The same list travels in GetSettings\n as `connectors`, next to the agents whose `connectorIds` select from it, so an assistant UI\n needs no separate call; this operation remains for a client that wants the list alone.\n Connect a not-yet-connected `user` connector via AuthorizeConnector.\n\n### Allowed Auth Scopes\n\n- wilma-assistant:ListConnectors\n- wilma-assistant:read","parameters":[{"name":"company","in":"query","required":false,"schema":{"type":"string","maxLength":30,"minLength":3,"description":"The unique identifier of the tenant when a service token is used."},"description":"The unique identifier of the tenant when a service token is used."},{"name":"user","in":"query","required":false,"schema":{"type":"string","maxLength":12,"minLength":12,"description":"The unique identifier of the user when a service or tenant token is used."},"description":"The unique identifier of the user when a service or tenant token is used."}]}},"/v2/assistant/connectors/{key}/authorize":{"post":{"operationId":"AuthorizeConnector","summary":"","description":"Starts connecting a `user`-mode connector for the current user. Returns a connect session\n the frontend uses to run the provider's authorization flow; on success the frontend calls\n ConfirmConnectorAuthorization.\n\n### Allowed Auth Scopes\n\n- wilma-assistant:AuthorizeConnector","parameters":[{"name":"key","in":"path","required":true,"schema":{"type":"string","maxLength":128,"minLength":1,"pattern":"^[a-z][a-z0-9._-]*$","description":"Stable identifier of a connector definition, such as `sentry`."},"description":"Stable identifier of a connector definition, such as `sentry`."},{"name":"company","in":"query","required":false,"schema":{"type":"string","maxLength":30,"minLength":3}},{"name":"user","in":"query","required":false,"schema":{"type":"string","maxLength":12,"minLength":12}}]},"delete":{"operationId":"RevokeConnectorAuthorization","summary":"","description":"Disconnects a `user`-mode connector for the current user, revoking the stored credential\n for the connector's provider. Idempotent — succeeds even if the user was not connected.\n Returns the connector with its now-disconnected state.\n\n### Allowed Auth Scopes\n\n- wilma-assistant:RevokeConnectorAuthorization","parameters":[{"name":"key","in":"path","required":true,"schema":{"type":"string","maxLength":128,"minLength":1,"pattern":"^[a-z][a-z0-9._-]*$","description":"Stable identifier of a connector definition, such as `sentry`."},"description":"Stable identifier of a connector definition, such as `sentry`."},{"name":"company","in":"query","required":false,"schema":{"type":"string","maxLength":30,"minLength":3}},{"name":"user","in":"query","required":false,"schema":{"type":"string","maxLength":12,"minLength":12}}]}},"/v2/assistant/connectors/{key}/authorize/complete":{"post":{"operationId":"ConfirmConnectorAuthorization","summary":"","description":"Completes connecting a `user`-mode connector after the authorization flow succeeds. Pass the\n connection id the flow returned. Returns the connector with its now-connected state.\n\n### Allowed Auth Scopes\n\n- wilma-assistant:ConfirmConnectorAuthorization","parameters":[{"name":"key","in":"path","required":true,"schema":{"type":"string","maxLength":128,"minLength":1,"pattern":"^[a-z][a-z0-9._-]*$","description":"Stable identifier of a connector definition, such as `sentry`."},"description":"Stable identifier of a connector definition, such as `sentry`."},{"name":"company","in":"query","required":false,"schema":{"type":"string","maxLength":30,"minLength":3}},{"name":"user","in":"query","required":false,"schema":{"type":"string","maxLength":12,"minLength":12}}],"requestBody":{"required":true,"schema":{"type":"object","properties":{"connectionId":{"type":"string","maxLength":256,"minLength":1,"description":"Connection id returned by the authorization flow."}},"required":["connectionId"]}}}},"/v2/assistant/sessions":{"get":{"operationId":"ListSessions","summary":"","description":"Lists durable assistant sessions for the authenticated owner. Hidden sessions (started by integrations) are excluded unless an explicit `visibility` filter is passed.\n\n### Allowed Auth Scopes\n\n- wilma-assistant:ListSessions\n- wilma-assistant:read","parameters":[{"name":"company","in":"query","required":false,"schema":{"type":"string","maxLength":30,"minLength":3,"description":"The unique identifier of the tenant when a service token is used."},"description":"The unique identifier of the tenant when a service token is used."},{"name":"user","in":"query","required":false,"schema":{"type":"string","maxLength":12,"minLength":12,"description":"The user the session belongs to. Optional: identifies the user when a service or tenant token is used; when omitted the session is not scoped to a specific user."},"description":"The user the session belongs to. Optional: identifies the user when a service or tenant token is used; when omitted the session is not scoped to a specific user."},{"name":"agentId","in":"query","required":false,"schema":{"type":"string","maxLength":12,"minLength":12}},{"name":"agentVersion","in":"query","required":false,"schema":{"type":"number","minimum":1,"description":"Only sessions served by this published agent version (rollout inspection)."},"description":"Only sessions served by this published agent version (rollout inspection)."},{"name":"status","in":"query","required":false,"schema":{"type":"string","enum":["running","idle","waiting","stopped","failed","terminated"]}},{"name":"visibility","in":"query","required":false,"schema":{"type":"string","description":"Whether a session shows up in the user's assistant history. Integration-driven sessions (e.g. copilot asset generation) are started `hidden` so they do not pollute the history while staying reachable by id.","enum":["listed","hidden"]},"description":"Only sessions with this visibility. Defaults to `listed`; pass `hidden` to inspect integration-driven sessions."},{"name":"target","in":"query","required":false,"schema":{"type":"string","maxLength":1024,"minLength":1,"description":"Only sessions with this exact target, e.g. `inbox://channels/ch_123` for the history of one conversation. Mutually exclusive with targetPrefix."},"description":"Only sessions with this exact target, e.g. `inbox://channels/ch_123` for the history of one conversation. Mutually exclusive with targetPrefix."},{"name":"targetPrefix","in":"query","required":false,"schema":{"type":"string","maxLength":1024,"minLength":1,"description":"Only sessions whose target starts with this prefix: a scheme for every session of one surface or integration (`si://`, `copilot://`), or a deeper path for a subtree (`si://calls/`). Mutually exclusive with target."},"description":"Only sessions whose target starts with this prefix: a scheme for every session of one surface or integration (`si://`, `copilot://`), or a deeper path for a subtree (`si://calls/`). Mutually exclusive with target."},{"name":"from","in":"query","required":false,"schema":{"type":"string","description":"Only sessions created at or after this time."},"description":"Only sessions created at or after this time."},{"name":"to","in":"query","required":false,"schema":{"type":"string","description":"Only sessions created before this time."},"description":"Only sessions created before this time."},{"name":"limit","in":"query","required":false,"schema":{"type":"number","maximum":100,"minimum":1}},{"name":"cursor","in":"query","required":false,"schema":{"type":"string","maxLength":2048,"minLength":1,"description":"Opaque cursor returned by a previous page; pass it back to fetch the next page."},"description":"Opaque cursor returned by a previous page; pass it back to fetch the next page."}]},"post":{"operationId":"StartSession","summary":"","description":"Starts a durable assistant session and returns the event index from which the caller should stream. Services integrating with the assistant (e.g. the copilot backend) start sessions on behalf of a user with `user`, a `target` naming the resource the session is for (e.g. `copilot://sessions/{copilotSessionId}`) and — for sessions that should not appear in the user's history — `visibility: hidden`.\n\n### Allowed Auth Scopes\n\n- wilma-assistant:StartSession","parameters":[{"name":"company","in":"query","required":false,"schema":{"type":"string","maxLength":30,"minLength":3,"description":"The unique identifier of the tenant when a service token is used."},"description":"The unique identifier of the tenant when a service token is used."}],"requestBody":{"required":true,"schema":{"type":"object","properties":{"user":{"type":"string","maxLength":12,"minLength":12,"description":"The user the session belongs to. Optional: identifies the user when a service or tenant token is used; when omitted the session is not scoped to a specific user."},"message":{"type":"string","maxLength":100000,"minLength":1},"attachments":{"type":"array","items":{"type":"string","maxLength":12,"minLength":12,"pattern":"^[A-Za-z0-9_-]{12}$","description":"Nano-id attachment identifier (12 characters)."},"description":"Ids of input attachments to include with the initial user message, as returned by UploadAttachment. Their stored metadata is reported back on the turn's MessageReceivedEvent."},"sessionId":{"type":"string","maxLength":128,"minLength":1},"agentId":{"type":"string","maxLength":12,"minLength":12},"agentVersion":{"type":"number","minimum":1,"description":"Published agent version to run. Mutually exclusive with agentDraft. When neither is set, the agent's deployment configuration selects the version."},"agentDraft":{"type":"boolean","description":"Run the agent's current draft (for testing). Mutually exclusive with agentVersion."},"options":{"description":"Runtime options for the session, e.g. `{model: 'google://gemini-3-flash-preview', timeZone: 'Europe/Rome'}`. `timeZone` is the IANA time zone of the person asking: the session shows every time in it, fixed at start, and falls back to the agent's when omitted. Interpreted by the runtime, so only documented keys belong here: put values the client wants back unchanged in `metadata`, and what the session belongs to in `target`."},"context":{"type":"array","items":{},"description":"What the client has in view and the server cannot read for itself, such as the page or the agent being edited, as `{id, type, data}` documents."},"mentions":{"type":"array","items":{"description":"Something attached to a message as agent context. A union, so a new kind of context is a new member rather than a change to every operation that carries mentions.","oneOf":[{"type":"object","title":"reference","properties":{"reference":{"type":"object","description":"A resource selected as agent context, identified by its canonical URI, e.g. `wilma://insights/calls/{callId}/flows/{flowIndex}`. It is read with the permissions of the session or task it is attached to; the URI grants no access by itself.","properties":{"uri":{"type":"string","maxLength":512,"minLength":1,"pattern":"^[a-z][a-z0-9+.-]*://.+$","description":"Canonical URI identifying one entity on the Wilma platform, e.g. 'wilma://agents/UXDT1W9fI3sj'\n or 'wilma://skills/aB3dE5fG7hJ9'. The path names the owning service and entity kind. Opaque to\n the contracts that carry it; non-Wilma schemes (records of external systems) fit the same shape."},"label":{"type":"string","maxLength":256,"description":"Display name of the mention chip. The model sees it only to name a mention that could not be read or was removed."}},"required":["uri"]}},"required":["reference"]}]},"maxItems":20,"description":"What the user attached for the agent to read, resolved with the session user's permissions, or company-wide when the session has no user."},"tools":{"type":"array","items":{"type":"object","description":"A tool the caller supplies for this session, rather than one configured on the agent. The\n routing fields (`fulfilment`, `gate`, `surface`, `stream`) are stamped onto the turn's\n InvocationStartedEvent, so clients replaying the session render the call correctly.","properties":{"name":{"type":"string"},"description":{"type":"string"},"schema":{},"fulfilment":{"type":"string","description":"Who produces an invocation's result. `server` tools execute inside the agent runtime.\n `client` tools are executed by the caller, so the turn suspends until it answers.\n `synthetic` tools never execute at all — they exist to put something on screen, and the\n runtime supplies their result itself, so the turn does not suspend.","enum":["server","client","synthetic"]},"gate":{"type":"string","description":"Whether the turn suspends on an invocation, and for what. Orthogonal to fulfilment: a\n `server` tool may be gated on a user decision (an approval), and a `synthetic` tool may be\n ungated (render and continue) or gated (an editable draft awaiting confirmation).","enum":["none","result","decision"]},"surface":{"type":"string","description":"Where a client should surface an invocation. Affects placement only, never behaviour.","enum":["hidden","activity","content"]},"stream":{"type":"boolean","description":"Emit InvocationDeltaEvent for calls to this tool, so the caller can render the input as the\n model writes it — useful when the input carries content rather than a lookup key. Defaults\n to false."},"ack":{"description":"Result handed back to the model for a `synthetic` tool, typically a short instruction\n telling the model what the user can now see. Ignored for `client` tools."}},"required":["name","schema"]}},"visibility":{"type":"string","description":"Whether a session shows up in the user's assistant history. Integration-driven sessions (e.g. copilot asset generation) are started `hidden` so they do not pollute the history while staying reachable by id.","enum":["listed","hidden"]},"target":{"type":"string","maxLength":1024,"minLength":1,"description":"What this session belongs to: the client surface it is started on (`inbox://channels/ch_123`) or the resource an integration starts it for (`copilot://sessions/cs_123`). Returned on SessionMeta and filterable on ListSessions, so a client can reopen the session where it came from and list a per-surface history. Opaque to the service; immutable once set."},"metadata":{"description":"Host-defined metadata to carry with the session and read back from SessionMeta. Not indexed; use `target` for anything a list must be filtered by."},"personalisation":{},"skills":{"type":"array","items":{"type":"object","description":"Ad-hoc skill supplied by the caller for this session only.","properties":{"name":{"type":"string"},"description":{"type":"string"},"body":{"type":"string"}},"required":["body","description","name"]}},"connectors":{"type":"object","description":"Per-session connector overrides, by connector instance id. `disabled` turns connectors off for this session only; `enabled` turns back on connectors the user disabled in their settings, for this session only.","properties":{"disabled":{"type":"array","items":{"type":"string","maxLength":12,"minLength":12,"pattern":"^[0-9A-Za-z]{12}$","description":"Unique identifier of a configured connector."}},"enabled":{"type":"array","items":{"type":"string","maxLength":12,"minLength":12,"pattern":"^[0-9A-Za-z]{12}$","description":"Unique identifier of a configured connector."}}}}},"required":["message"]}}}},"/v2/assistant/sessions/items/{sessionId}":{"get":{"operationId":"GetSession","summary":"","description":"Gets the durable state of an assistant session.\n\n### Allowed Auth Scopes\n\n- wilma-assistant:GetSession\n- wilma-assistant:read","parameters":[{"name":"sessionId","in":"path","required":true,"schema":{"type":"string","maxLength":128,"minLength":1}},{"name":"company","in":"query","required":false,"schema":{"type":"string","maxLength":30,"minLength":3,"description":"The unique identifier of the tenant when a service token is used."},"description":"The unique identifier of the tenant when a service token is used."},{"name":"user","in":"query","required":false,"schema":{"type":"string","maxLength":12,"minLength":12,"description":"The user the session belongs to. Optional: identifies the user when a service or tenant token is used; when omitted the session is not scoped to a specific user."},"description":"The user the session belongs to. Optional: identifies the user when a service or tenant token is used; when omitted the session is not scoped to a specific user."}]},"delete":{"operationId":"DeleteSession","summary":"","description":"Permanently deletes a durable assistant session and its event log. Idempotent: deleting an already-deleted session succeeds. Observability data recorded in wilma-traces is unaffected.\n\n### Allowed Auth Scopes\n\n- wilma-assistant:DeleteSession","parameters":[{"name":"sessionId","in":"path","required":true,"schema":{"type":"string","maxLength":128,"minLength":1}},{"name":"company","in":"query","required":false,"schema":{"type":"string","maxLength":30,"minLength":3,"description":"The unique identifier of the tenant when a service token is used."},"description":"The unique identifier of the tenant when a service token is used."},{"name":"user","in":"query","required":false,"schema":{"type":"string","maxLength":12,"minLength":12,"description":"The user the session belongs to. Optional: identifies the user when a service or tenant token is used; when omitted the session is not scoped to a specific user."},"description":"The user the session belongs to. Optional: identifies the user when a service or tenant token is used; when omitted the session is not scoped to a specific user."}]}},"/v2/assistant/sessions/items/{sessionId}/events":{"get":{"operationId":"GetSessionEvents","summary":"","description":"Returns recorded session events as a plain JSON page, without streaming. Use this to fetch the activity of past sessions or to catch up before subscribing to StreamSessionEvents. Events of a currently running turn become visible here at turn boundaries; subscribe to StreamSessionEvents for live delivery.\n\n### Allowed Auth Scopes\n\n- wilma-assistant:GetSessionEvents\n- wilma-assistant:read","parameters":[{"name":"sessionId","in":"path","required":true,"schema":{"type":"string","maxLength":128,"minLength":1}},{"name":"company","in":"query","required":false,"schema":{"type":"string","maxLength":30,"minLength":3,"description":"The unique identifier of the tenant when a service token is used."},"description":"The unique identifier of the tenant when a service token is used."},{"name":"user","in":"query","required":false,"schema":{"type":"string","maxLength":12,"minLength":12,"description":"The user the session belongs to. Optional: identifies the user when a service or tenant token is used; when omitted the session is not scoped to a specific user."},"description":"The user the session belongs to. Optional: identifies the user when a service or tenant token is used; when omitted the session is not scoped to a specific user."},{"name":"startIndex","in":"query","required":false,"schema":{"type":"number","minimum":0,"description":"Return events with `index >= startIndex`. Defaults to 0."},"description":"Return events with `index >= startIndex`. Defaults to 0."},{"name":"limit","in":"query","required":false,"schema":{"type":"number","maximum":1000,"minimum":1,"description":"Maximum number of events to return. Defaults to 1000."},"description":"Maximum number of events to return. Defaults to 1000."}]}},"/v2/assistant/sessions/items/{sessionId}/events/stream":{"get":{"operationId":"StreamSessionEvents","summary":"","description":"Replays and tails session events as Server-Sent Events (`text/event-stream`). Each frame carries one AssistantSessionEvent: `id:` is the event index, `event:` is the union member name, and `data:` is the JSON document of the union value. Reconnect by passing the last seen index + 1 as `startIndex`. The stream ends after a terminal event (idle / waiting / stopped / failed) of the tailed turn; for finished sessions the recorded events are replayed and the stream closes.\n\n### Allowed Auth Scopes\n\n- wilma-assistant:StreamSessionEvents\n- wilma-assistant:read","parameters":[{"name":"sessionId","in":"path","required":true,"schema":{"type":"string","maxLength":128,"minLength":1}},{"name":"company","in":"query","required":false,"schema":{"type":"string","maxLength":30,"minLength":3,"description":"The unique identifier of the tenant when a service token is used."},"description":"The unique identifier of the tenant when a service token is used."},{"name":"user","in":"query","required":false,"schema":{"type":"string","maxLength":12,"minLength":12,"description":"The user the session belongs to. Optional: identifies the user when a service or tenant token is used; when omitted the session is not scoped to a specific user."},"description":"The user the session belongs to. Optional: identifies the user when a service or tenant token is used; when omitted the session is not scoped to a specific user."},{"name":"startIndex","in":"query","required":false,"schema":{"type":"number","minimum":0,"description":"Replay recorded events with `index >= startIndex` before tailing live ones. Defaults to 0."},"description":"Replay recorded events with `index >= startIndex` before tailing live ones. Defaults to 0."}]}},"/v2/assistant/sessions/items/{sessionId}/resume":{"post":{"operationId":"ResumeSession","summary":"","description":"Resolves pending session input and resumes agent execution.\n\n### Allowed Auth Scopes\n\n- wilma-assistant:ResumeSession","parameters":[{"name":"sessionId","in":"path","required":true,"schema":{"type":"string","maxLength":128,"minLength":1}},{"name":"company","in":"query","required":false,"schema":{"type":"string","maxLength":30,"minLength":3,"description":"The unique identifier of the tenant when a service token is used."},"description":"The unique identifier of the tenant when a service token is used."},{"name":"user","in":"query","required":false,"schema":{"type":"string","maxLength":12,"minLength":12,"description":"The user the session belongs to. Optional: identifies the user when a service or tenant token is used; when omitted the session is not scoped to a specific user."},"description":"The user the session belongs to. Optional: identifies the user when a service or tenant token is used; when omitted the session is not scoped to a specific user."}],"requestBody":{"required":true,"schema":{"type":"object","properties":{"pendingId":{"type":"string"},"inputResponses":{"type":"array","items":{"type":"object","properties":{"callId":{"type":"string"},"result":{},"approved":{"type":"boolean"}},"required":["callId"]}}},"required":["inputResponses","pendingId"]}}}},"/v2/assistant/sessions/items/{sessionId}/steer":{"post":{"operationId":"SteerSession","summary":"","description":"Adds a message to the turn already running, without discarding its work: the runtime injects\n it at the next model step boundary (a tool call already in flight finishes first). A session\n that is `waiting` on caller input cannot be steered and fails with a conflict — answer its\n pending invocations via ResumeSession; use ContinueSession for an `idle`, `stopped` or `failed`\n session. If the turn finishes before the steer is injected, the message runs as a new turn\n instead; `outcome` reports which happened, and the response carries the index to stream from.\n\n### Allowed Auth Scopes\n\n- wilma-assistant:SteerSession","parameters":[{"name":"sessionId","in":"path","required":true,"schema":{"type":"string","maxLength":128,"minLength":1}},{"name":"company","in":"query","required":false,"schema":{"type":"string","maxLength":30,"minLength":3,"description":"The unique identifier of the tenant when a service token is used."},"description":"The unique identifier of the tenant when a service token is used."},{"name":"user","in":"query","required":false,"schema":{"type":"string","maxLength":12,"minLength":12,"description":"The user the session belongs to. Optional: identifies the user when a service or tenant token is used; when omitted the session is not scoped to a specific user."},"description":"The user the session belongs to. Optional: identifies the user when a service or tenant token is used; when omitted the session is not scoped to a specific user."}],"requestBody":{"required":true,"schema":{"type":"object","properties":{"message":{"type":"string","maxLength":100000,"minLength":1},"attachments":{"type":"array","items":{"type":"string","maxLength":12,"minLength":12,"pattern":"^[A-Za-z0-9_-]{12}$","description":"Nano-id attachment identifier (12 characters)."},"description":"Input attachments to include with the steering message. Fetch them via the Attachments API."},"context":{"type":"array","items":{},"description":"Replaces the session's context documents, what the client has in view and the server cannot read for itself, from the next model step. Omit to keep them."},"mentions":{"type":"array","items":{"description":"Something attached to a message as agent context. A union, so a new kind of context is a new member rather than a change to every operation that carries mentions.","oneOf":[{"type":"object","title":"reference","properties":{"reference":{"type":"object","description":"A resource selected as agent context, identified by its canonical URI, e.g. `wilma://insights/calls/{callId}/flows/{flowIndex}`. It is read with the permissions of the session or task it is attached to; the URI grants no access by itself.","properties":{"uri":{"type":"string","maxLength":512,"minLength":1,"pattern":"^[a-z][a-z0-9+.-]*://.+$","description":"Canonical URI identifying one entity on the Wilma platform, e.g. 'wilma://agents/UXDT1W9fI3sj'\n or 'wilma://skills/aB3dE5fG7hJ9'. The path names the owning service and entity kind. Opaque to\n the contracts that carry it; non-Wilma schemes (records of external systems) fit the same shape."},"label":{"type":"string","maxLength":256,"description":"Display name of the mention chip. The model sees it only to name a mention that could not be read or was removed."}},"required":["uri"]}},"required":["reference"]}]},"maxItems":20,"description":"Replaces the session's mentions from the next model step. Omit to keep them; pass an empty list to clear them."}},"required":["message"]}}}},"/v2/assistant/sessions/items/{sessionId}/stop":{"post":{"operationId":"StopSession","summary":"","description":"Requests cancellation of the active turn in a durable assistant session.\n\n### Allowed Auth Scopes\n\n- wilma-assistant:StopSession","parameters":[{"name":"sessionId","in":"path","required":true,"schema":{"type":"string","maxLength":128,"minLength":1}},{"name":"company","in":"query","required":false,"schema":{"type":"string","maxLength":30,"minLength":3,"description":"The unique identifier of the tenant when a service token is used."},"description":"The unique identifier of the tenant when a service token is used."},{"name":"user","in":"query","required":false,"schema":{"type":"string","maxLength":12,"minLength":12,"description":"The user the session belongs to. Optional: identifies the user when a service or tenant token is used; when omitted the session is not scoped to a specific user."},"description":"The user the session belongs to. Optional: identifies the user when a service or tenant token is used; when omitted the session is not scoped to a specific user."}]}},"/v2/assistant/sessions/items/{sessionId}/turns":{"post":{"operationId":"ContinueSession","summary":"","description":"Starts another turn in an existing durable assistant session.\n\n### Allowed Auth Scopes\n\n- wilma-assistant:ContinueSession","parameters":[{"name":"sessionId","in":"path","required":true,"schema":{"type":"string","maxLength":128,"minLength":1}},{"name":"company","in":"query","required":false,"schema":{"type":"string","maxLength":30,"minLength":3,"description":"The unique identifier of the tenant when a service token is used."},"description":"The unique identifier of the tenant when a service token is used."},{"name":"user","in":"query","required":false,"schema":{"type":"string","maxLength":12,"minLength":12,"description":"The user the session belongs to. Optional: identifies the user when a service or tenant token is used; when omitted the session is not scoped to a specific user."},"description":"The user the session belongs to. Optional: identifies the user when a service or tenant token is used; when omitted the session is not scoped to a specific user."}],"requestBody":{"required":true,"schema":{"type":"object","properties":{"message":{"type":"string","maxLength":100000,"minLength":1},"attachments":{"type":"array","items":{"type":"string","maxLength":12,"minLength":12,"pattern":"^[A-Za-z0-9_-]{12}$","description":"Nano-id attachment identifier (12 characters)."},"description":"Ids of input attachments to include with this user message, as returned by UploadAttachment. Their stored metadata is reported back on the turn's MessageReceivedEvent."},"context":{"type":"array","items":{},"description":"Replaces the session's context documents: what the client has in view and the server cannot read for itself. Omit to keep them."},"mentions":{"type":"array","items":{"description":"Something attached to a message as agent context. A union, so a new kind of context is a new member rather than a change to every operation that carries mentions.","oneOf":[{"type":"object","title":"reference","properties":{"reference":{"type":"object","description":"A resource selected as agent context, identified by its canonical URI, e.g. `wilma://insights/calls/{callId}/flows/{flowIndex}`. It is read with the permissions of the session or task it is attached to; the URI grants no access by itself.","properties":{"uri":{"type":"string","maxLength":512,"minLength":1,"pattern":"^[a-z][a-z0-9+.-]*://.+$","description":"Canonical URI identifying one entity on the Wilma platform, e.g. 'wilma://agents/UXDT1W9fI3sj'\n or 'wilma://skills/aB3dE5fG7hJ9'. The path names the owning service and entity kind. Opaque to\n the contracts that carry it; non-Wilma schemes (records of external systems) fit the same shape."},"label":{"type":"string","maxLength":256,"description":"Display name of the mention chip. The model sees it only to name a mention that could not be read or was removed."}},"required":["uri"]}},"required":["reference"]}]},"maxItems":20,"description":"Replaces the session's mentions. Omit to keep them; pass an empty list to clear them."},"tools":{"type":"array","items":{"type":"object","description":"A tool the caller supplies for this session, rather than one configured on the agent. The\n routing fields (`fulfilment`, `gate`, `surface`, `stream`) are stamped onto the turn's\n InvocationStartedEvent, so clients replaying the session render the call correctly.","properties":{"name":{"type":"string"},"description":{"type":"string"},"schema":{},"fulfilment":{"type":"string","description":"Who produces an invocation's result. `server` tools execute inside the agent runtime.\n `client` tools are executed by the caller, so the turn suspends until it answers.\n `synthetic` tools never execute at all — they exist to put something on screen, and the\n runtime supplies their result itself, so the turn does not suspend.","enum":["server","client","synthetic"]},"gate":{"type":"string","description":"Whether the turn suspends on an invocation, and for what. Orthogonal to fulfilment: a\n `server` tool may be gated on a user decision (an approval), and a `synthetic` tool may be\n ungated (render and continue) or gated (an editable draft awaiting confirmation).","enum":["none","result","decision"]},"surface":{"type":"string","description":"Where a client should surface an invocation. Affects placement only, never behaviour.","enum":["hidden","activity","content"]},"stream":{"type":"boolean","description":"Emit InvocationDeltaEvent for calls to this tool, so the caller can render the input as the\n model writes it — useful when the input carries content rather than a lookup key. Defaults\n to false."},"ack":{"description":"Result handed back to the model for a `synthetic` tool, typically a short instruction\n telling the model what the user can now see. Ignored for `client` tools."}},"required":["name","schema"]}},"skills":{"type":"array","items":{"type":"object","description":"Ad-hoc skill supplied by the caller for this session only.","properties":{"name":{"type":"string"},"description":{"type":"string"},"body":{"type":"string"}},"required":["body","description","name"]}},"connectors":{"type":"object","description":"Per-session connector overrides, by connector instance id. `disabled` turns connectors off for this session only; `enabled` turns back on connectors the user disabled in their settings, for this session only.","properties":{"disabled":{"type":"array","items":{"type":"string","maxLength":12,"minLength":12,"pattern":"^[0-9A-Za-z]{12}$","description":"Unique identifier of a configured connector."}},"enabled":{"type":"array","items":{"type":"string","maxLength":12,"minLength":12,"pattern":"^[0-9A-Za-z]{12}$","description":"Unique identifier of a configured connector."}}}}},"required":["message"]}}}},"/v2/assistant/settings":{"get":{"operationId":"GetSettings","summary":"","description":"Gets AI assistant settings for the user. Returns personalisation fields (style, about),\n the agents, skills and connectors available to the user, and per-user skill and connector\n preferences. Each agent's `connectorIds` say which of the `connectors` it calls, and\n `copilotSettings` says whether Copilot may assist the user.\n\n### Allowed Auth Scopes\n\n- wilma-assistant:GetSettings\n- wilma-assistant:read","parameters":[{"name":"company","in":"query","required":false,"schema":{"type":"string","maxLength":30,"minLength":3,"description":"The unique identifier of the tenant when a service token is used."},"description":"The unique identifier of the tenant when a service token is used."},{"name":"user","in":"query","required":false,"schema":{"type":"string","maxLength":12,"minLength":12,"description":"The unique identifier of the user when a service or tenant token is used."},"description":"The unique identifier of the user when a service or tenant token is used."}]},"patch":{"operationId":"PatchSettings","summary":"","description":"Updates AI assistant settings for the user. Only supplied fields are changed.\n Returns the effective settings after the update, including the current agent and\n skill catalog.\n\n### Allowed Auth Scopes\n\n- wilma-assistant:PatchSettings","parameters":[{"name":"company","in":"query","required":false,"schema":{"type":"string","maxLength":30,"minLength":3,"description":"The unique identifier of the tenant when a service token is used."},"description":"The unique identifier of the tenant when a service token is used."},{"name":"user","in":"query","required":false,"schema":{"type":"string","maxLength":12,"minLength":12,"description":"The unique identifier of the user when a service or tenant token is used."},"description":"The unique identifier of the user when a service or tenant token is used."}],"requestBody":{"required":false,"schema":{"type":"object","properties":{"style":{"type":"object","description":"How the assistant should communicate with this user.","properties":{"tone":{"type":"string","maxLength":64,"minLength":1}}},"about":{"type":"object","description":"Background the assistant may use to personalise replies for this user.","properties":{"description":{"type":"string","maxLength":2000,"minLength":1}}},"skills":{"type":"object","description":"Per-user skill preferences. Skills the user has disabled are omitted from the effective skill set at runtime.","properties":{"disabled":{"type":"array","items":{"type":"string","maxLength":12,"minLength":12}}}},"connectors":{"type":"object","description":"Per-user connector preferences, by connector instance id. Connectors the user has disabled are removed from the effective set at runtime, even when an admin enabled them.","properties":{"disabled":{"type":"array","items":{"type":"string","maxLength":12,"minLength":12,"pattern":"^[0-9A-Za-z]{12}$","description":"Unique identifier of a configured connector."}}}}}}}}},"/v2/audit/events":{"get":{"operationId":"QueryAuditEvents","summary":"","description":"Queries immutable audit events newest first. Filter by resource URI (exact\n or prefix), actor, action, time range or indexed event attributes. Results\n carry enough data to render a resource history view.\n\n### Allowed Auth Scopes\n\n- wilma-audit:QueryAuditEvents\n- wilma-audit:read","parameters":[{"name":"company","in":"query","required":false,"schema":{"type":"string","maxLength":30,"minLength":3}},{"name":"uri","in":"query","required":false,"schema":{"type":"string","maxLength":512,"minLength":1,"pattern":"^[a-z][a-z0-9+.-]*://.+$","description":"Only events about this exact resource, e.g. 'wilma://agents/UXDT1W9fI3sj'. Mutually exclusive with uriPrefix."},"description":"Only events about this exact resource, e.g. 'wilma://agents/UXDT1W9fI3sj'. Mutually exclusive with uriPrefix."},{"name":"uriPrefix","in":"query","required":false,"schema":{"type":"string","maxLength":512,"minLength":1,"pattern":"^[a-z][a-z0-9+.-]*://.*$","description":"Only events whose resource URI starts with this prefix, e.g. 'wilma://agents/' for every agent event or 'wilma://' for every Wilma resource. Mutually exclusive with uri."},"description":"Only events whose resource URI starts with this prefix, e.g. 'wilma://agents/' for every agent event or 'wilma://' for every Wilma resource. Mutually exclusive with uri."},{"name":"action","in":"query","required":false,"schema":{"type":"string"}},{"name":"actorId","in":"query","required":false,"schema":{"type":"string","description":"Filter by the user, API key or service that performed the action."},"description":"Filter by the user, API key or service that performed the action."},{"name":"actorType","in":"query","required":false,"schema":{"type":"string","enum":["user","api_key","service"]}},{"name":"from","in":"query","required":false,"schema":{"type":"string","description":"ISO-8601 timestamp with millisecond precision, e.g. '2026-07-10T14:12:30.123Z'."},"description":"ISO-8601 timestamp with millisecond precision, e.g. '2026-07-10T14:12:30.123Z'."},{"name":"to","in":"query","required":false,"schema":{"type":"string","description":"ISO-8601 timestamp with millisecond precision, e.g. '2026-07-10T14:12:30.123Z'."},"description":"ISO-8601 timestamp with millisecond precision, e.g. '2026-07-10T14:12:30.123Z'."},{"name":"limit","in":"query","required":false,"schema":{"type":"number","maximum":100,"minimum":1}},{"name":"cursor","in":"query","required":false,"schema":{"type":"string","maxLength":2048,"minLength":1,"description":"Opaque cursor returned by a previous page; pass it back to fetch the next page."},"description":"Opaque cursor returned by a previous page; pass it back to fetch the next page."},{"name":"filter","in":"query","required":false,"schema":{"type":"array","items":{"type":"string","description":"One attribute filter, as `key=value` (e.g. `agent.id=UXDT1W9fI3sj`). An operation may accept\n more forms; its `filters` member (query `filter`) says which."},"description":"Audit event attribute equality filters, one `key=value` per entry."},"description":"Audit event attribute equality filters, one `key=value` per entry."}]}},"/v2/audit/events/{eventId}":{"get":{"operationId":"GetAuditEvent","summary":"","description":"Returns a single audit event by id.\n\n### Allowed Auth Scopes\n\n- wilma-audit:GetAuditEvent\n- wilma-audit:read","parameters":[{"name":"eventId","in":"path","required":true,"schema":{"type":"string","maxLength":128,"minLength":1,"description":"Opaque audit event identifier generated by the audit service."},"description":"Opaque audit event identifier generated by the audit service."},{"name":"company","in":"query","required":false,"schema":{"type":"string","maxLength":30,"minLength":3}}]}},"/v2/audit/references":{"get":{"operationId":"QueryReferences","summary":"","description":"Returns the resources that reference the given entity — where it is used. Only the current\n surface of each referrer is considered: its draft and its latest published version; references\n present only in a draft are flagged on the returned entry. An entity nothing references (or an\n unknown uri) yields an empty list.\n\n### Allowed Auth Scopes\n\n- wilma-audit:QueryReferences\n- wilma-audit:read","parameters":[{"name":"company","in":"query","required":false,"schema":{"type":"string","maxLength":30,"minLength":3}},{"name":"uri","in":"query","required":true,"schema":{"type":"string","maxLength":512,"minLength":1,"pattern":"^[a-z][a-z0-9+.-]*://.+$","description":"The entity to find references to, e.g. 'wilma://skills/aB3dE5fG7hJ9'."},"description":"The entity to find references to, e.g. 'wilma://skills/aB3dE5fG7hJ9'."},{"name":"limit","in":"query","required":false,"schema":{"type":"number","maximum":100,"minimum":1}},{"name":"cursor","in":"query","required":false,"schema":{"type":"string","maxLength":2048,"minLength":1,"description":"Opaque cursor returned by a previous page; pass it back to fetch the next page."},"description":"Opaque cursor returned by a previous page; pass it back to fetch the next page."}]}},"/v2/copilot/assets":{"get":{"operationId":"ListAssets","summary":"","description":"Lists reusable assets in the company.\n\n### Allowed Auth Scopes\n\n- wilma-copilot:ListAssets\n- wilma-copilot:read","parameters":[{"name":"company","in":"query","required":false,"schema":{"type":"string","maxLength":30,"minLength":3}},{"name":"contentType","in":"query","required":false,"schema":{"type":"string","description":"Which kind of content an asset carries.","enum":["card","playbook","smart_action","knowledge"]},"description":"Filter by content kind."},{"name":"enabled","in":"query","required":false,"schema":{"type":"boolean","description":"Filter by enabled state."},"description":"Filter by enabled state."},{"name":"search","in":"query","required":false,"schema":{"type":"string","description":"Filter by name or description."},"description":"Filter by name or description."},{"name":"limit","in":"query","required":false,"schema":{"type":"number","maximum":100,"minimum":1}},{"name":"cursor","in":"query","required":false,"schema":{"type":"string","maxLength":2048,"minLength":1,"description":"Opaque cursor returned by a previous page; pass it back to fetch the next page."},"description":"Opaque cursor returned by a previous page; pass it back to fetch the next page."}]},"post":{"operationId":"CreateAsset","summary":"","description":"Creates a new reusable asset.\n\n### Allowed Auth Scopes\n\n- wilma-copilot:CreateAsset","parameters":[{"name":"company","in":"query","required":false,"schema":{"type":"string","maxLength":30,"minLength":3}}],"requestBody":{"required":true,"schema":{"type":"object","description":"The editable definition of an asset.","properties":{"key":{"type":"string","description":"Stable programmatic key referenced from suggestions and traces."},"name":{"type":"string"},"description":{"type":"string"},"enabled":{"type":"boolean","description":"Whether the asset participates in matching. Defaults to true."},"title":{"type":"string","description":"Card headline."},"subtitle":{"type":"string"},"markdown":{"type":"string","description":"Markdown body shown on the card."},"footer":{"type":"string"},"severity":{"type":"string","description":"Visual emphasis of the asset card.","enum":["neutral","success","warning","danger"]},"icon":{"type":"string","description":"Icon shown on the asset card.","enum":["info","lightbulb","shield","book_open","alert","sparkles"]},"sticky":{"type":"boolean","description":"Keep the asset pinned in the UI rather than dismissible."},"content":{"description":"The kind-specific payload of an asset. Exactly one member is set.","oneOf":[{"type":"object","title":"card","properties":{"card":{"type":"object","description":"An informational card. `content` selects how the card body is produced (static markdown or agent-generated); the headline and markdown members live on the asset definition.","properties":{"content":{"description":"How a card's body is produced. Exactly one member is set.","oneOf":[{"type":"object","title":"static","properties":{"static":{"type":"object","description":"Static markdown card content.","properties":{"markdown":{"type":"string"}}}},"required":["static"]},{"type":"object","title":"agent","properties":{"agent":{"type":"object","description":"Card content generated on demand by an agent.","properties":{"agentId":{"type":"string","maxLength":12,"minLength":12},"variables":{"type":"array","items":{"type":"object","description":"Binds an input variable of an agent or tool invocation to a value or a fill strategy.","properties":{"name":{"type":"string"},"type":{"type":"string","description":"Declared value type, e.g. 'string' or 'number'."},"optional":{"type":"boolean"},"variant":{"type":"string","enum":["auto","guided","predefined"]},"description":{"type":"string","description":"Guidance for the model when variant is `guided`."},"value":{"type":"string","description":"Fixed value when variant is `predefined`. Supports handlebars against the session state, e.g. '{{call.remotePhone}}'."}},"required":["name","variant"]},"description":"Variables passed to the agent when generating the card."}},"required":["agentId"]}},"required":["agent"]}]}}}},"required":["card"]},{"type":"object","title":"playbook","properties":{"playbook":{"type":"object","description":"A step-by-step playbook for the agent to follow.","properties":{"goal":{"type":"string"},"steps":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"title":{"type":"string"},"markdown":{"type":"string"},"required":{"type":"boolean","description":"Whether the step must be completed."},"autoComplete":{"type":"boolean","description":"Whether the system marks the step complete automatically from the transcript. While the\n playbook is shown and the step is open, a decision model asks whether it is done, judging\n only what was said after the playbook was shown."},"autoCompleteInstructions":{"type":"string","description":"What makes the step complete, when autoComplete is set. The decision model reads it as the yes/no it answers."}},"required":["id","title"]}},"completionCriteria":{"description":"How a playbook is judged complete. Exactly one member is set.","oneOf":[{"type":"object","title":"manual","properties":{"manual":{"type":"object","description":"The playbook is completed manually by the agent.","properties":{"allowAgentMarkComplete":{"type":"boolean"}},"required":["allowAgentMarkComplete"]}},"required":["manual"]},{"type":"object","title":"semantic","properties":{"semantic":{"type":"object","description":"The playbook is completed automatically when a decision model judges its goal met, judging\n only what was said after the playbook was shown.","properties":{"description":{"type":"string"}},"required":["description"]}},"required":["semantic"]}]}}}},"required":["playbook"]},{"type":"object","title":"smartAction","properties":{"smartAction":{"type":"object","description":"A set of one-click smart actions offered to the agent.","properties":{"actions":{"type":"array","items":{"type":"object","description":"A smart action with its label and payload.","properties":{"id":{"type":"string"},"label":{"type":"string"},"description":{"type":"string"},"action":{"description":"A button shown under an asset. Exactly one member is set; it selects what happens when clicked.","oneOf":[{"type":"object","title":"openUrl","properties":{"openUrl":{"type":"object","description":"Opens a URL for the agent.","properties":{"url":{"type":"string"},"openInNewTab":{"type":"boolean"}},"required":["url"]}},"required":["openUrl"]},{"type":"object","title":"runAgent","properties":{"runAgent":{"type":"object","description":"Runs a Wilma agent.","properties":{"agentId":{"type":"string","maxLength":12,"minLength":12},"variables":{"type":"array","items":{"type":"object","description":"Binds an input variable of an agent or tool invocation to a value or a fill strategy.","properties":{"name":{"type":"string"},"type":{"type":"string","description":"Declared value type, e.g. 'string' or 'number'."},"optional":{"type":"boolean"},"variant":{"type":"string","enum":["auto","guided","predefined"]},"description":{"type":"string","description":"Guidance for the model when variant is `guided`."},"value":{"type":"string","description":"Fixed value when variant is `predefined`. Supports handlebars against the session state, e.g. '{{call.remotePhone}}'."}},"required":["name","variant"]}},"resultMode":{"type":"string","description":"Where a smart action's result is delivered.","enum":["show_card","silent"]},"requireConfirmation":{"type":"boolean","description":"Ask the agent to confirm before running. Defaults to false."}},"required":["agentId","resultMode"]}},"required":["runAgent"]},{"type":"object","title":"transfer","properties":{"transfer":{"type":"object","description":"Transfers the live call to a destination. Voice calls only.","properties":{"destination":{"type":"string","description":"Extension or phone number to transfer to."}},"required":["destination"]}},"required":["transfer"]},{"type":"object","title":"handover","properties":{"handover":{"type":"object","description":"Hands the live chat over to an agent or service. Chats only.","properties":{"handoverVariant":{"type":"string","description":"Target of a chat handover smart action.","enum":["agent","service"]},"assignee":{"type":"string","description":"Wilma agent to hand over to when handoverVariant is `agent`."},"service":{"type":"string","description":"x-bees service to hand over to when handoverVariant is `service`."}},"required":["handoverVariant"]}},"required":["handover"]}]}},"required":["action","id","label"]}}},"required":["actions"]}},"required":["smartAction"]},{"type":"object","title":"knowledge","properties":{"knowledge":{"type":"object","description":"A knowledge answer with its supporting sources.","properties":{"summary":{"type":"string"},"sources":{"type":"string"}}}},"required":["knowledge"]}]},"conditions":{"description":"Conditions that show the asset. It is shown at most once per session when they first become true."}},"required":["content","name"]}}}},"/v2/copilot/assets/{assetId}":{"get":{"operationId":"GetAsset","summary":"","description":"Gets an asset.\n\n### Allowed Auth Scopes\n\n- wilma-copilot:GetAsset\n- wilma-copilot:read","parameters":[{"name":"assetId","in":"path","required":true,"schema":{"type":"string","maxLength":128,"minLength":1}},{"name":"company","in":"query","required":false,"schema":{"type":"string","maxLength":30,"minLength":3}}]},"put":{"operationId":"UpdateAsset","summary":"","description":"Updates an asset. Published profile versions that captured this asset are unaffected; the change reaches live conversations when a profile referencing it is republished.\n\n### Allowed Auth Scopes\n\n- wilma-copilot:UpdateAsset","parameters":[{"name":"assetId","in":"path","required":true,"schema":{"type":"string","maxLength":128,"minLength":1}},{"name":"company","in":"query","required":false,"schema":{"type":"string","maxLength":30,"minLength":3}}],"requestBody":{"required":true,"schema":{"type":"object","description":"The editable definition of an asset.","properties":{"key":{"type":"string","description":"Stable programmatic key referenced from suggestions and traces."},"name":{"type":"string"},"description":{"type":"string"},"enabled":{"type":"boolean","description":"Whether the asset participates in matching. Defaults to true."},"title":{"type":"string","description":"Card headline."},"subtitle":{"type":"string"},"markdown":{"type":"string","description":"Markdown body shown on the card."},"footer":{"type":"string"},"severity":{"type":"string","description":"Visual emphasis of the asset card.","enum":["neutral","success","warning","danger"]},"icon":{"type":"string","description":"Icon shown on the asset card.","enum":["info","lightbulb","shield","book_open","alert","sparkles"]},"sticky":{"type":"boolean","description":"Keep the asset pinned in the UI rather than dismissible."},"content":{"description":"The kind-specific payload of an asset. Exactly one member is set.","oneOf":[{"type":"object","title":"card","properties":{"card":{"type":"object","description":"An informational card. `content` selects how the card body is produced (static markdown or agent-generated); the headline and markdown members live on the asset definition.","properties":{"content":{"description":"How a card's body is produced. Exactly one member is set.","oneOf":[{"type":"object","title":"static","properties":{"static":{"type":"object","description":"Static markdown card content.","properties":{"markdown":{"type":"string"}}}},"required":["static"]},{"type":"object","title":"agent","properties":{"agent":{"type":"object","description":"Card content generated on demand by an agent.","properties":{"agentId":{"type":"string","maxLength":12,"minLength":12},"variables":{"type":"array","items":{"type":"object","description":"Binds an input variable of an agent or tool invocation to a value or a fill strategy.","properties":{"name":{"type":"string"},"type":{"type":"string","description":"Declared value type, e.g. 'string' or 'number'."},"optional":{"type":"boolean"},"variant":{"type":"string","enum":["auto","guided","predefined"]},"description":{"type":"string","description":"Guidance for the model when variant is `guided`."},"value":{"type":"string","description":"Fixed value when variant is `predefined`. Supports handlebars against the session state, e.g. '{{call.remotePhone}}'."}},"required":["name","variant"]},"description":"Variables passed to the agent when generating the card."}},"required":["agentId"]}},"required":["agent"]}]}}}},"required":["card"]},{"type":"object","title":"playbook","properties":{"playbook":{"type":"object","description":"A step-by-step playbook for the agent to follow.","properties":{"goal":{"type":"string"},"steps":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"title":{"type":"string"},"markdown":{"type":"string"},"required":{"type":"boolean","description":"Whether the step must be completed."},"autoComplete":{"type":"boolean","description":"Whether the system marks the step complete automatically from the transcript. While the\n playbook is shown and the step is open, a decision model asks whether it is done, judging\n only what was said after the playbook was shown."},"autoCompleteInstructions":{"type":"string","description":"What makes the step complete, when autoComplete is set. The decision model reads it as the yes/no it answers."}},"required":["id","title"]}},"completionCriteria":{"description":"How a playbook is judged complete. Exactly one member is set.","oneOf":[{"type":"object","title":"manual","properties":{"manual":{"type":"object","description":"The playbook is completed manually by the agent.","properties":{"allowAgentMarkComplete":{"type":"boolean"}},"required":["allowAgentMarkComplete"]}},"required":["manual"]},{"type":"object","title":"semantic","properties":{"semantic":{"type":"object","description":"The playbook is completed automatically when a decision model judges its goal met, judging\n only what was said after the playbook was shown.","properties":{"description":{"type":"string"}},"required":["description"]}},"required":["semantic"]}]}}}},"required":["playbook"]},{"type":"object","title":"smartAction","properties":{"smartAction":{"type":"object","description":"A set of one-click smart actions offered to the agent.","properties":{"actions":{"type":"array","items":{"type":"object","description":"A smart action with its label and payload.","properties":{"id":{"type":"string"},"label":{"type":"string"},"description":{"type":"string"},"action":{"description":"A button shown under an asset. Exactly one member is set; it selects what happens when clicked.","oneOf":[{"type":"object","title":"openUrl","properties":{"openUrl":{"type":"object","description":"Opens a URL for the agent.","properties":{"url":{"type":"string"},"openInNewTab":{"type":"boolean"}},"required":["url"]}},"required":["openUrl"]},{"type":"object","title":"runAgent","properties":{"runAgent":{"type":"object","description":"Runs a Wilma agent.","properties":{"agentId":{"type":"string","maxLength":12,"minLength":12},"variables":{"type":"array","items":{"type":"object","description":"Binds an input variable of an agent or tool invocation to a value or a fill strategy.","properties":{"name":{"type":"string"},"type":{"type":"string","description":"Declared value type, e.g. 'string' or 'number'."},"optional":{"type":"boolean"},"variant":{"type":"string","enum":["auto","guided","predefined"]},"description":{"type":"string","description":"Guidance for the model when variant is `guided`."},"value":{"type":"string","description":"Fixed value when variant is `predefined`. Supports handlebars against the session state, e.g. '{{call.remotePhone}}'."}},"required":["name","variant"]}},"resultMode":{"type":"string","description":"Where a smart action's result is delivered.","enum":["show_card","silent"]},"requireConfirmation":{"type":"boolean","description":"Ask the agent to confirm before running. Defaults to false."}},"required":["agentId","resultMode"]}},"required":["runAgent"]},{"type":"object","title":"transfer","properties":{"transfer":{"type":"object","description":"Transfers the live call to a destination. Voice calls only.","properties":{"destination":{"type":"string","description":"Extension or phone number to transfer to."}},"required":["destination"]}},"required":["transfer"]},{"type":"object","title":"handover","properties":{"handover":{"type":"object","description":"Hands the live chat over to an agent or service. Chats only.","properties":{"handoverVariant":{"type":"string","description":"Target of a chat handover smart action.","enum":["agent","service"]},"assignee":{"type":"string","description":"Wilma agent to hand over to when handoverVariant is `agent`."},"service":{"type":"string","description":"x-bees service to hand over to when handoverVariant is `service`."}},"required":["handoverVariant"]}},"required":["handover"]}]}},"required":["action","id","label"]}}},"required":["actions"]}},"required":["smartAction"]},{"type":"object","title":"knowledge","properties":{"knowledge":{"type":"object","description":"A knowledge answer with its supporting sources.","properties":{"summary":{"type":"string"},"sources":{"type":"string"}}}},"required":["knowledge"]}]},"conditions":{"description":"Conditions that show the asset. It is shown at most once per session when they first become true."},"revision":{"type":"number","description":"Revision this update is based on (optimistic locking)."}},"required":["content","name","revision"]}}},"delete":{"operationId":"DeleteAsset","summary":"","description":"Deletes an asset and removes it from every profile draft that references it. Fails with ConflictException when a profile draft still references it and detach is not allowed.\n\n### Allowed Auth Scopes\n\n- wilma-copilot:DeleteAsset","parameters":[{"name":"assetId","in":"path","required":true,"schema":{"type":"string","maxLength":128,"minLength":1}},{"name":"company","in":"query","required":false,"schema":{"type":"string","maxLength":30,"minLength":3}},{"name":"detach","in":"query","required":false,"schema":{"type":"boolean","description":"Detach the asset from referencing profile drafts instead of failing with ConflictException."},"description":"Detach the asset from referencing profile drafts instead of failing with ConflictException."}]}},"/v2/copilot/automations":{"get":{"operationId":"ListAutomations","summary":"","description":"Lists reusable automations in the company.\n\n### Allowed Auth Scopes\n\n- wilma-copilot:ListAutomations\n- wilma-copilot:read","parameters":[{"name":"company","in":"query","required":false,"schema":{"type":"string","maxLength":30,"minLength":3}},{"name":"search","in":"query","required":false,"schema":{"type":"string","description":"Filter by name or description."},"description":"Filter by name or description."},{"name":"limit","in":"query","required":false,"schema":{"type":"number","maximum":100,"minimum":1}},{"name":"cursor","in":"query","required":false,"schema":{"type":"string","maxLength":2048,"minLength":1,"description":"Opaque cursor returned by a previous page; pass it back to fetch the next page."},"description":"Opaque cursor returned by a previous page; pass it back to fetch the next page."}]},"post":{"operationId":"CreateAutomation","summary":"","description":"Creates a new reusable automation.\n\n### Allowed Auth Scopes\n\n- wilma-copilot:CreateAutomation","parameters":[{"name":"company","in":"query","required":false,"schema":{"type":"string","maxLength":30,"minLength":3}}],"requestBody":{"required":true,"schema":{"type":"object","description":"The editable definition of an automation.","properties":{"name":{"type":"string"},"description":{"type":"string"},"handler":{"description":"What the automation does when it fires. Exactly one member is set.","oneOf":[{"type":"object","title":"agent","properties":{"agent":{"type":"object","description":"Runs a Wilma agent and optionally shows its generated content to the agent.","properties":{"agentId":{"type":"string","maxLength":12,"minLength":12},"prompt":{"type":"string","description":"Prompt handed to the agent; the live conversation context is added automatically."},"outputMode":{"type":"string","description":"Where the automation's result is delivered.","enum":["card","silent"]},"parameters":{"type":"array","items":{"type":"object","description":"Binds an input variable of an agent or tool invocation to a value or a fill strategy.","properties":{"name":{"type":"string"},"type":{"type":"string","description":"Declared value type, e.g. 'string' or 'number'."},"optional":{"type":"boolean"},"variant":{"type":"string","enum":["auto","guided","predefined"]},"description":{"type":"string","description":"Guidance for the model when variant is `guided`."},"value":{"type":"string","description":"Fixed value when variant is `predefined`. Supports handlebars against the session state, e.g. '{{call.remotePhone}}'."}},"required":["name","variant"]}}},"required":["agentId"]}},"required":["agent"]}]},"conditions":{"description":"Conditions that trigger the automation. It fires at most once per session when they first become true."}},"required":["handler","name"]}}}},"/v2/copilot/automations/{automationId}":{"get":{"operationId":"GetAutomation","summary":"","description":"Gets an automation.\n\n### Allowed Auth Scopes\n\n- wilma-copilot:GetAutomation\n- wilma-copilot:read","parameters":[{"name":"automationId","in":"path","required":true,"schema":{"type":"string","maxLength":128,"minLength":1}},{"name":"company","in":"query","required":false,"schema":{"type":"string","maxLength":30,"minLength":3}}]},"put":{"operationId":"UpdateAutomation","summary":"","description":"Updates an automation. Published profile versions that captured this automation are unaffected; the change reaches live conversations when a profile referencing it is republished.\n\n### Allowed Auth Scopes\n\n- wilma-copilot:UpdateAutomation","parameters":[{"name":"automationId","in":"path","required":true,"schema":{"type":"string","maxLength":128,"minLength":1}},{"name":"company","in":"query","required":false,"schema":{"type":"string","maxLength":30,"minLength":3}}],"requestBody":{"required":true,"schema":{"type":"object","description":"The editable definition of an automation.","properties":{"name":{"type":"string"},"description":{"type":"string"},"handler":{"description":"What the automation does when it fires. Exactly one member is set.","oneOf":[{"type":"object","title":"agent","properties":{"agent":{"type":"object","description":"Runs a Wilma agent and optionally shows its generated content to the agent.","properties":{"agentId":{"type":"string","maxLength":12,"minLength":12},"prompt":{"type":"string","description":"Prompt handed to the agent; the live conversation context is added automatically."},"outputMode":{"type":"string","description":"Where the automation's result is delivered.","enum":["card","silent"]},"parameters":{"type":"array","items":{"type":"object","description":"Binds an input variable of an agent or tool invocation to a value or a fill strategy.","properties":{"name":{"type":"string"},"type":{"type":"string","description":"Declared value type, e.g. 'string' or 'number'."},"optional":{"type":"boolean"},"variant":{"type":"string","enum":["auto","guided","predefined"]},"description":{"type":"string","description":"Guidance for the model when variant is `guided`."},"value":{"type":"string","description":"Fixed value when variant is `predefined`. Supports handlebars against the session state, e.g. '{{call.remotePhone}}'."}},"required":["name","variant"]}}},"required":["agentId"]}},"required":["agent"]}]},"conditions":{"description":"Conditions that trigger the automation. It fires at most once per session when they first become true."},"revision":{"type":"number","description":"Revision this update is based on (optimistic locking)."}},"required":["handler","name","revision"]}}},"delete":{"operationId":"DeleteAutomation","summary":"","description":"Deletes an automation and removes it from every profile draft that references it. Fails with ConflictException when a profile draft still references it and detach is not allowed.\n\n### Allowed Auth Scopes\n\n- wilma-copilot:DeleteAutomation","parameters":[{"name":"automationId","in":"path","required":true,"schema":{"type":"string","maxLength":128,"minLength":1}},{"name":"company","in":"query","required":false,"schema":{"type":"string","maxLength":30,"minLength":3}},{"name":"detach","in":"query","required":false,"schema":{"type":"boolean","description":"Detach the automation from referencing profile drafts instead of failing with ConflictException."},"description":"Detach the automation from referencing profile drafts instead of failing with ConflictException."}]}},"/v2/copilot/evaluations":{"get":{"operationId":"ListEvaluations","summary":"","description":"Lists reusable evaluations in the company.\n\n### Allowed Auth Scopes\n\n- wilma-copilot:ListEvaluations\n- wilma-copilot:read","parameters":[{"name":"company","in":"query","required":false,"schema":{"type":"string","maxLength":30,"minLength":3}},{"name":"group","in":"query","required":false,"schema":{"type":"string","description":"Filter by grouping label."},"description":"Filter by grouping label."},{"name":"search","in":"query","required":false,"schema":{"type":"string","description":"Filter by name or description."},"description":"Filter by name or description."},{"name":"limit","in":"query","required":false,"schema":{"type":"number","maximum":100,"minimum":1}},{"name":"cursor","in":"query","required":false,"schema":{"type":"string","maxLength":2048,"minLength":1,"description":"Opaque cursor returned by a previous page; pass it back to fetch the next page."},"description":"Opaque cursor returned by a previous page; pass it back to fetch the next page."}]},"post":{"operationId":"CreateEvaluation","summary":"","description":"Creates a new reusable evaluation.\n\n### Allowed Auth Scopes\n\n- wilma-copilot:CreateEvaluation","parameters":[{"name":"company","in":"query","required":false,"schema":{"type":"string","maxLength":30,"minLength":3}}],"requestBody":{"required":true,"schema":{"type":"object","description":"The editable definition of an evaluation.","properties":{"name":{"type":"string","maxLength":128,"minLength":1,"pattern":"^[a-z0-9](?:[a-z0-9_]*[a-z0-9])?$","description":"Programmatic evaluation name used in the session state and condition fields, e.g. 'csat'."},"description":{"type":"string"},"instructions":{"type":"string","description":"Prompt the model follows to score the transcript."},"fields":{"type":"array","items":{"type":"object","description":"A named value the model extracts from the transcript, shared by intent AI matching and evaluations.","properties":{"id":{"type":"string"},"name":{"type":"string"},"description":{"type":"string"},"optional":{"type":"boolean","description":"Whether the model may leave this field unset. Defaults to true."},"parameter":{"description":"The value shape of a field. Exactly one member is set; the member selects how the model extracts and validates the value.","oneOf":[{"type":"object","title":"text","properties":{"text":{"type":"object","description":"Free-form single-line text value.","properties":{}}},"required":["text"]},{"type":"object","title":"multiline","properties":{"multiline":{"type":"object","description":"Free-form multi-line text value.","properties":{}}},"required":["multiline"]},{"type":"object","title":"number","properties":{"number":{"type":"object","description":"Numeric value with optional bounds.","properties":{"min":{"type":"number"},"max":{"type":"number"}}}},"required":["number"]},{"type":"object","title":"selection","properties":{"selection":{"type":"object","description":"Value chosen from a fixed (or AI-extended) set of options.","properties":{"options":{"type":"array","items":{"type":"string"},"description":"The allowed options."},"multi":{"type":"boolean","description":"Whether more than one option may be selected."},"adaptive":{"type":"boolean","description":"Whether the model may introduce new options beyond `options`."},"adaptiveOptions":{"type":"array","items":{"type":"string"},"description":"Options the model introduced when `adaptive` is enabled."}},"required":["options"]}},"required":["selection"]}]}},"required":["id","name","parameter"]},"description":"Structured values the evaluation produces. A `selection` field is answered directly by a\n decision model; a `text`, `multiline` or `number` field is extracted by a language model,\n only once the decision model says the value was stated."},"conditions":{"description":"Extra conditions gating whether this evaluation runs for a session."}},"required":["instructions","name"]}}}},"/v2/copilot/evaluations/{evaluationId}":{"get":{"operationId":"GetEvaluation","summary":"","description":"Gets an evaluation.\n\n### Allowed Auth Scopes\n\n- wilma-copilot:GetEvaluation\n- wilma-copilot:read","parameters":[{"name":"evaluationId","in":"path","required":true,"schema":{"type":"string","maxLength":128,"minLength":1}},{"name":"company","in":"query","required":false,"schema":{"type":"string","maxLength":30,"minLength":3}}]},"put":{"operationId":"UpdateEvaluation","summary":"","description":"Updates an evaluation. Published profile versions that captured this evaluation are unaffected; the change reaches live conversations when a profile referencing it is republished.\n\n### Allowed Auth Scopes\n\n- wilma-copilot:UpdateEvaluation","parameters":[{"name":"evaluationId","in":"path","required":true,"schema":{"type":"string","maxLength":128,"minLength":1}},{"name":"company","in":"query","required":false,"schema":{"type":"string","maxLength":30,"minLength":3}}],"requestBody":{"required":true,"schema":{"type":"object","description":"The editable definition of an evaluation.","properties":{"name":{"type":"string","maxLength":128,"minLength":1,"pattern":"^[a-z0-9](?:[a-z0-9_]*[a-z0-9])?$","description":"Programmatic evaluation name used in the session state and condition fields, e.g. 'csat'."},"description":{"type":"string"},"instructions":{"type":"string","description":"Prompt the model follows to score the transcript."},"fields":{"type":"array","items":{"type":"object","description":"A named value the model extracts from the transcript, shared by intent AI matching and evaluations.","properties":{"id":{"type":"string"},"name":{"type":"string"},"description":{"type":"string"},"optional":{"type":"boolean","description":"Whether the model may leave this field unset. Defaults to true."},"parameter":{"description":"The value shape of a field. Exactly one member is set; the member selects how the model extracts and validates the value.","oneOf":[{"type":"object","title":"text","properties":{"text":{"type":"object","description":"Free-form single-line text value.","properties":{}}},"required":["text"]},{"type":"object","title":"multiline","properties":{"multiline":{"type":"object","description":"Free-form multi-line text value.","properties":{}}},"required":["multiline"]},{"type":"object","title":"number","properties":{"number":{"type":"object","description":"Numeric value with optional bounds.","properties":{"min":{"type":"number"},"max":{"type":"number"}}}},"required":["number"]},{"type":"object","title":"selection","properties":{"selection":{"type":"object","description":"Value chosen from a fixed (or AI-extended) set of options.","properties":{"options":{"type":"array","items":{"type":"string"},"description":"The allowed options."},"multi":{"type":"boolean","description":"Whether more than one option may be selected."},"adaptive":{"type":"boolean","description":"Whether the model may introduce new options beyond `options`."},"adaptiveOptions":{"type":"array","items":{"type":"string"},"description":"Options the model introduced when `adaptive` is enabled."}},"required":["options"]}},"required":["selection"]}]}},"required":["id","name","parameter"]},"description":"Structured values the evaluation produces. A `selection` field is answered directly by a\n decision model; a `text`, `multiline` or `number` field is extracted by a language model,\n only once the decision model says the value was stated."},"conditions":{"description":"Extra conditions gating whether this evaluation runs for a session."},"revision":{"type":"number","description":"Revision this update is based on (optimistic locking)."}},"required":["instructions","name","revision"]}}},"delete":{"operationId":"DeleteEvaluation","summary":"","description":"Deletes an evaluation and removes it from every profile draft that references it. Fails with ConflictException when a profile draft still references it and detach is not allowed.\n\n### Allowed Auth Scopes\n\n- wilma-copilot:DeleteEvaluation","parameters":[{"name":"evaluationId","in":"path","required":true,"schema":{"type":"string","maxLength":128,"minLength":1}},{"name":"company","in":"query","required":false,"schema":{"type":"string","maxLength":30,"minLength":3}},{"name":"detach","in":"query","required":false,"schema":{"type":"boolean","description":"Detach the evaluation from referencing profile drafts instead of failing with ConflictException."},"description":"Detach the evaluation from referencing profile drafts instead of failing with ConflictException."}]}},"/v2/copilot/intents":{"get":{"operationId":"ListIntents","summary":"","description":"Lists reusable intents in the company.\n\n### Allowed Auth Scopes\n\n- wilma-copilot:ListIntents\n- wilma-copilot:read","parameters":[{"name":"company","in":"query","required":false,"schema":{"type":"string","maxLength":30,"minLength":3}},{"name":"search","in":"query","required":false,"schema":{"type":"string","description":"Filter by name or description."},"description":"Filter by name or description."},{"name":"limit","in":"query","required":false,"schema":{"type":"number","maximum":100,"minimum":1}},{"name":"cursor","in":"query","required":false,"schema":{"type":"string","maxLength":2048,"minLength":1,"description":"Opaque cursor returned by a previous page; pass it back to fetch the next page."},"description":"Opaque cursor returned by a previous page; pass it back to fetch the next page."}]},"post":{"operationId":"CreateIntent","summary":"","description":"Creates a new reusable intent.\n\n### Allowed Auth Scopes\n\n- wilma-copilot:CreateIntent","parameters":[{"name":"company","in":"query","required":false,"schema":{"type":"string","maxLength":30,"minLength":3}}],"requestBody":{"required":true,"schema":{"type":"object","description":"The editable definition of an intent.","properties":{"name":{"type":"string","maxLength":128,"minLength":1,"pattern":"^[a-z0-9](?:[a-z0-9_]*[a-z0-9])?$","description":"Programmatic intent name used in the session state and condition fields, e.g. 'refund_request'."},"description":{"type":"string"},"handlers":{"type":"object","description":"The detection handlers for an intent. Text matching is attempted first, then AI matching.\n\n An absent handler is a matcher that is off - which is what the `enabled` flag on each\n handler used to say. An intent with neither handler set is never detected.","properties":{"textMatch":{"type":"object","description":"Literal-text matching. Checked before AI matching; a text hit short-circuits semantic evaluation.","properties":{"options":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"value":{"type":"string","description":"The phrase, word or pattern to look for, e.g. 'refund', 'W-AIR' or a regex."},"mode":{"type":"string","description":"How a literal phrase is compared against the transcript.","enum":["exact","fuzzy","regex"]}},"required":["id","mode","value"]}}}},"aiMatch":{"type":"object","description":"Semantic (AI) matching. Detects the intent from meaning and extracts structured fields into\n `intent.<name>.<field>`.\n\n A decision model answers whether the intent happened, and whether each field is mentioned; the\n language model is called only to extract the values of fields that are.","properties":{"instructions":{"type":"string","description":"Prompt describing when this intent applies."},"fields":{"type":"array","items":{"type":"object","description":"A named value the model extracts from the transcript, shared by intent AI matching and evaluations.","properties":{"id":{"type":"string"},"name":{"type":"string"},"description":{"type":"string"},"optional":{"type":"boolean","description":"Whether the model may leave this field unset. Defaults to true."},"parameter":{"description":"The value shape of a field. Exactly one member is set; the member selects how the model extracts and validates the value.","oneOf":[{"type":"object","title":"text","properties":{"text":{"type":"object","description":"Free-form single-line text value.","properties":{}}},"required":["text"]},{"type":"object","title":"multiline","properties":{"multiline":{"type":"object","description":"Free-form multi-line text value.","properties":{}}},"required":["multiline"]},{"type":"object","title":"number","properties":{"number":{"type":"object","description":"Numeric value with optional bounds.","properties":{"min":{"type":"number"},"max":{"type":"number"}}}},"required":["number"]},{"type":"object","title":"selection","properties":{"selection":{"type":"object","description":"Value chosen from a fixed (or AI-extended) set of options.","properties":{"options":{"type":"array","items":{"type":"string"},"description":"The allowed options."},"multi":{"type":"boolean","description":"Whether more than one option may be selected."},"adaptive":{"type":"boolean","description":"Whether the model may introduce new options beyond `options`."},"adaptiveOptions":{"type":"array","items":{"type":"string"},"description":"Options the model introduced when `adaptive` is enabled."}},"required":["options"]}},"required":["selection"]}]}},"required":["id","name","parameter"]},"description":"Structured values the model extracts when the intent matches."}}},"saidBy":{"type":"string","description":"Whose lines a transcript-reading resource is matched against.\n\n On an intent, which is the only copilot resource that reads the transcript itself: a\n profile does not listen, and an asset reacts to what an intent already decided. It matters\n for the intents that are about behaviour rather than topic - \"the customer asked for a\n refund\" and \"the agent offered a refund\" are different events, and matching over the\n whole transcript makes them one. The side of each transcript line is already resolved by\n the runtime (`role: agent | customer`).","enum":["anyone","agent","customer"]}}},"conditions":{"description":"Extra conditions gating whether this intent is evaluated for a session."}},"required":["handlers","name"]}}}},"/v2/copilot/intents/{intentId}":{"get":{"operationId":"GetIntent","summary":"","description":"Gets an intent.\n\n### Allowed Auth Scopes\n\n- wilma-copilot:GetIntent\n- wilma-copilot:read","parameters":[{"name":"intentId","in":"path","required":true,"schema":{"type":"string","maxLength":128,"minLength":1}},{"name":"company","in":"query","required":false,"schema":{"type":"string","maxLength":30,"minLength":3}}]},"put":{"operationId":"UpdateIntent","summary":"","description":"Updates an intent. Published profile versions that captured this intent are unaffected; the change reaches live conversations when a profile referencing it is republished.\n\n### Allowed Auth Scopes\n\n- wilma-copilot:UpdateIntent","parameters":[{"name":"intentId","in":"path","required":true,"schema":{"type":"string","maxLength":128,"minLength":1}},{"name":"company","in":"query","required":false,"schema":{"type":"string","maxLength":30,"minLength":3}}],"requestBody":{"required":true,"schema":{"type":"object","description":"The editable definition of an intent.","properties":{"name":{"type":"string","maxLength":128,"minLength":1,"pattern":"^[a-z0-9](?:[a-z0-9_]*[a-z0-9])?$","description":"Programmatic intent name used in the session state and condition fields, e.g. 'refund_request'."},"description":{"type":"string"},"handlers":{"type":"object","description":"The detection handlers for an intent. Text matching is attempted first, then AI matching.\n\n An absent handler is a matcher that is off - which is what the `enabled` flag on each\n handler used to say. An intent with neither handler set is never detected.","properties":{"textMatch":{"type":"object","description":"Literal-text matching. Checked before AI matching; a text hit short-circuits semantic evaluation.","properties":{"options":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"value":{"type":"string","description":"The phrase, word or pattern to look for, e.g. 'refund', 'W-AIR' or a regex."},"mode":{"type":"string","description":"How a literal phrase is compared against the transcript.","enum":["exact","fuzzy","regex"]}},"required":["id","mode","value"]}}}},"aiMatch":{"type":"object","description":"Semantic (AI) matching. Detects the intent from meaning and extracts structured fields into\n `intent.<name>.<field>`.\n\n A decision model answers whether the intent happened, and whether each field is mentioned; the\n language model is called only to extract the values of fields that are.","properties":{"instructions":{"type":"string","description":"Prompt describing when this intent applies."},"fields":{"type":"array","items":{"type":"object","description":"A named value the model extracts from the transcript, shared by intent AI matching and evaluations.","properties":{"id":{"type":"string"},"name":{"type":"string"},"description":{"type":"string"},"optional":{"type":"boolean","description":"Whether the model may leave this field unset. Defaults to true."},"parameter":{"description":"The value shape of a field. Exactly one member is set; the member selects how the model extracts and validates the value.","oneOf":[{"type":"object","title":"text","properties":{"text":{"type":"object","description":"Free-form single-line text value.","properties":{}}},"required":["text"]},{"type":"object","title":"multiline","properties":{"multiline":{"type":"object","description":"Free-form multi-line text value.","properties":{}}},"required":["multiline"]},{"type":"object","title":"number","properties":{"number":{"type":"object","description":"Numeric value with optional bounds.","properties":{"min":{"type":"number"},"max":{"type":"number"}}}},"required":["number"]},{"type":"object","title":"selection","properties":{"selection":{"type":"object","description":"Value chosen from a fixed (or AI-extended) set of options.","properties":{"options":{"type":"array","items":{"type":"string"},"description":"The allowed options."},"multi":{"type":"boolean","description":"Whether more than one option may be selected."},"adaptive":{"type":"boolean","description":"Whether the model may introduce new options beyond `options`."},"adaptiveOptions":{"type":"array","items":{"type":"string"},"description":"Options the model introduced when `adaptive` is enabled."}},"required":["options"]}},"required":["selection"]}]}},"required":["id","name","parameter"]},"description":"Structured values the model extracts when the intent matches."}}},"saidBy":{"type":"string","description":"Whose lines a transcript-reading resource is matched against.\n\n On an intent, which is the only copilot resource that reads the transcript itself: a\n profile does not listen, and an asset reacts to what an intent already decided. It matters\n for the intents that are about behaviour rather than topic - \"the customer asked for a\n refund\" and \"the agent offered a refund\" are different events, and matching over the\n whole transcript makes them one. The side of each transcript line is already resolved by\n the runtime (`role: agent | customer`).","enum":["anyone","agent","customer"]}}},"conditions":{"description":"Extra conditions gating whether this intent is evaluated for a session."},"revision":{"type":"number","description":"Revision this update is based on (optimistic locking)."}},"required":["handlers","name","revision"]}}},"delete":{"operationId":"DeleteIntent","summary":"","description":"Deletes an intent and removes it from every profile draft that references it. Fails with ConflictException when a profile draft still references it and detach is not allowed.\n\n### Allowed Auth Scopes\n\n- wilma-copilot:DeleteIntent","parameters":[{"name":"intentId","in":"path","required":true,"schema":{"type":"string","maxLength":128,"minLength":1}},{"name":"company","in":"query","required":false,"schema":{"type":"string","maxLength":30,"minLength":3}},{"name":"detach","in":"query","required":false,"schema":{"type":"boolean","description":"Detach the intent from referencing profile drafts instead of failing with ConflictException."},"description":"Detach the intent from referencing profile drafts instead of failing with ConflictException."}]}},"/v2/copilot/profiles":{"get":{"operationId":"ListProfiles","summary":"","description":"Lists copilot profiles in the company.\n\n### Allowed Auth Scopes\n\n- wilma-copilot:ListProfiles\n- wilma-copilot:read","parameters":[{"name":"company","in":"query","required":false,"schema":{"type":"string","maxLength":30,"minLength":3}},{"name":"status","in":"query","required":false,"schema":{"type":"string","enum":["draft","active"]},"description":"Filter by status."},{"name":"channel","in":"query","required":false,"schema":{"type":"string","description":"The kind of conversation a copilot resource applies to.\n\n Singular members and no wildcard, unlike the `calls | chats | any` field this replaces -\n and `conference` is nameable for the first time. Note that this is the *conversation*\n channel, not the Wilma runtime channel (`assistant` / `chat` / `voice`) and not the\n messaging service a chat arrived on (which is `chat.service`).","enum":["call","conference","chat"]},"description":"Filter by a conversation channel the profile applies to. A profile with no channels applies to every one, so it matches whatever is asked for here."},{"name":"stage","in":"query","required":false,"schema":{"type":"string","description":"Where in a conversation's life a copilot resource applies.\n\n `before` is the pre-conversation session copilot opens from a client lookup (`Open` in\n `wildix.wilma.copilot.sessions`): a customer is on screen and no call, conference or chat\n exists yet. `during` is a live conversation. `after` is everything from the conversation\n ending onwards - wrap-up, and the post-conversation summary that carries CSAT and topics.\n\n A conversation that has not started has no channel and no queue, which is the one rule the\n other two members of this triple follow from: see `CopilotChannelList` and\n `CopilotAudienceKind`.","enum":["before","during","after"]},"description":"Filter by a stage the profile applies in. A profile with no stages applies in every one, so it matches whatever is asked for here."},{"name":"search","in":"query","required":false,"schema":{"type":"string","description":"Filter by name or description."},"description":"Filter by name or description."},{"name":"limit","in":"query","required":false,"schema":{"type":"number","maximum":100,"minimum":1}},{"name":"cursor","in":"query","required":false,"schema":{"type":"string","maxLength":2048,"minLength":1,"description":"Opaque cursor returned by a previous page; pass it back to fetch the next page."},"description":"Opaque cursor returned by a previous page; pass it back to fetch the next page."}]},"post":{"operationId":"CreateProfile","summary":"","description":"Creates a new profile draft. The profile is not applied to any conversation until its first version is published.\n\n### Allowed Auth Scopes\n\n- wilma-copilot:CreateProfile","parameters":[{"name":"company","in":"query","required":false,"schema":{"type":"string","maxLength":30,"minLength":3}}],"requestBody":{"required":true,"schema":{"type":"object","description":"The editable definition of a profile: metadata plus attached features and library resources.\n\n A session asks a decision model at most 200 configured questions, counted across every profile\n that applies to it, a question shared by two profiles counting once: each topic category and\n topic, built-in or custom, each included signal, each intent and each evaluation. Questions copilot\n adds on its own (field guards, playbook steps, closing detection) do not count. Past the limit,\n the questions are sorted by their `<kind>.<name>` key (`evaluations.upsell`, `intents.refund`,\n `signals.escalation_requested`, `topics.billing`); those after the first 200 are not asked, and\n the session trace says which.","properties":{"name":{"type":"string"},"description":{"type":"string"},"audience":{"type":"array","items":{"type":"object","description":"One audience entry: a kind, and the values of that kind the profile assists. An entry with no values does not narrow anything.","properties":{"kind":{"type":"string","description":"The kind of thing a profile is aimed at.\n\n Two families, and the difference decides which stages the entry can constrain.\n `user`, `group`, `department` and `agent` name **who is assisted** - a person for the first\n three, a Wilma AI agent for the last - and in the `before` stage the assisted party is\n whoever opened copilot, so they constrain every stage. `queue`, `service` and `trunk`\n describe a **live conversation**, which does not exist in `before` - so an entry of those\n kinds says nothing there.\n\n Values are the tenant's own routing objects: user emails for `user`, Wilma agent ids for\n `agent`, PBX group and department ids, queue and trunk names as `wda-stream` reports them,\n and x-bees service uris for `service`.","enum":["user","group","department","agent","queue","service","trunk"]},"values":{"type":"array","items":{"type":"string"}}},"required":["kind","values"]},"description":"Who this profile assists - queues, groups, departments, users, AI agents, chat services\n and trunks. Empty or absent means everyone in the company. This is the profile's primary\n scope: an administrator reaches for \"my refunds team\", not for a conversation medium,\n and a queue already carries the medium, the skill and the line of business."},"stages":{"type":"array","items":{"type":"string","description":"Where in a conversation's life a copilot resource applies.\n\n `before` is the pre-conversation session copilot opens from a client lookup (`Open` in\n `wildix.wilma.copilot.sessions`): a customer is on screen and no call, conference or chat\n exists yet. `during` is a live conversation. `after` is everything from the conversation\n ending onwards - wrap-up, and the post-conversation summary that carries CSAT and topics.\n\n A conversation that has not started has no channel and no queue, which is the one rule the\n other two members of this triple follow from: see `CopilotChannelList` and\n `CopilotAudienceKind`.","enum":["before","during","after"]},"description":"Stages of a conversation this profile applies in. Empty or absent means every stage."},"channels":{"type":"array","items":{"type":"string","description":"The kind of conversation a copilot resource applies to.\n\n Singular members and no wildcard, unlike the `calls | chats | any` field this replaces -\n and `conference` is nameable for the first time. Note that this is the *conversation*\n channel, not the Wilma runtime channel (`assistant` / `chat` / `voice`) and not the\n messaging service a chat arrived on (which is `chat.service`).","enum":["call","conference","chat"]},"description":"Conversation channels this profile applies to. Empty or absent means every channel.\n\n Only constrains the `during` and `after` stages - nothing is a call, a conference or a\n chat before it starts - so a profile whose `stages` are `before` alone applies whatever\n this says."},"conditions":{"deprecated":true,"description":"Conditions under which the profile matches a conversation. An empty tree always matches. Deprecated - see the deprecation note.\nThis shape is deprecated since 2026-08: Profile conditions are resolved once, on the session's first event, so a rule over anything copilot has yet to detect never activates the profile. Use `audience` / `stages` / `channels` for what is known at that moment, and put everything conditional on the assets, intents and automations the profile carries, where conditions are re-evaluated as state changes. Still read for profiles authored before those members existed."},"features":{"type":"array","items":{"description":"A Copilot feature enabled for a profile, with its type-specific configuration.","oneOf":[{"type":"object","title":"csat","properties":{"csat":{"type":"object","description":"Configuration for the built-in CSAT feature."}},"required":["csat"]},{"type":"object","title":"sentiment","properties":{"sentiment":{"type":"object","description":"Configuration for the built-in sentiment feature."}},"required":["sentiment"]},{"type":"object","title":"talkRatio","properties":{"talkRatio":{"type":"object","description":"Configuration for the built-in talk ratio feature."}},"required":["talkRatio"]},{"type":"object","title":"topics","properties":{"topics":{"type":"object","description":"Configuration for the built-in topics feature.\n\n Topics are classified, not summarised: a decision model answers, for each category and topic,\n whether the conversation covers it, and `topics` holds the names of the categories and topics it\n does - values a condition can compare. A category is asked on every full-transcript pass until it\n is detected; its topics are asked once it is. A category's name is in `topics` when the category\n itself is detected, not when one of its topics is.\n\n `custom` adds the company's own: new categories with their topics, and topics added to a\n built-in category, such as `mastercard` in `payment`. Each category and topic, built-in or\n custom, counts towards the session's question limit.","properties":{"include":{"type":"array","items":{"type":"string","maxLength":64,"minLength":1,"pattern":"^[a-z0-9](?:[a-z0-9_]*[a-z0-9])?$","description":"A name from a built-in catalog: a group (a topic category, a signal group) or a single label\n in one. The catalog lives with the service, which rejects a name it does not know, so new groups\n and labels need no SDK release. A company's own topic categories and topics use the same form."},"maxItems":256,"description":"Built-in categories, or single topics in them, to classify against. Empty or absent means every category."},"custom":{"type":"array","items":{"type":"object","description":"A topic category of the company's own, or the company's topics in a built-in category. A new\n category is asked like a built-in one, and its topics once it is detected.","properties":{"name":{"type":"string","maxLength":64,"minLength":1,"pattern":"^[a-z0-9](?:[a-z0-9_]*[a-z0-9])?$","description":"A new category's name, or the name of the built-in category its topics are added to."},"description":{"type":"string","maxLength":500,"minLength":1,"description":"What a new category covers, as the decision model reads it: required for a new category,\n and left out when extending a built-in one, which keeps its own."},"topics":{"type":"array","items":{"type":"object","description":"A topic of the company's own, in the category that holds it.","properties":{"name":{"type":"string","maxLength":64,"minLength":1,"pattern":"^[a-z0-9](?:[a-z0-9_]*[a-z0-9])?$","description":"The topic's name in `topics` and in conditions; not a name the catalog already uses."},"description":{"type":"string","maxLength":500,"minLength":1,"description":"What the conversation covers when this topic applies, as the decision model reads it, e.g. 'Customer pays, or asks about paying, by Mastercard'."}},"required":["description","name"]},"maxItems":64,"minItems":1}},"required":["name","topics"]},"maxItems":32,"description":"The company's own categories, and the topics it adds to built-in ones; at most 64 topics in\n all. A name is used once across `custom`, and only a category that extends a built-in one\n uses a catalog name. Topics added to a built-in category are asked like its own: once it is\n detected when the whole category is included, from the start when only some of its topics\n are. A built-in category that `include` leaves out entirely cannot be extended."}}}},"required":["topics"]},{"type":"object","title":"contextualKnowledgeAnswers","properties":{"contextualKnowledgeAnswers":{"type":"object","description":"Configuration for the built-in contextual knowledge answers feature.","properties":{"source":{"type":"string","description":"Where contextual-knowledge answers are sourced from.","enum":["knowledge_bases","agent"]},"knowledgeBaseIds":{"type":"array","items":{"type":"string","description":"Id of a knowledge base referenced by copilot. Managed in the wim-knowledge-base service."},"description":"Knowledge bases used when source is `knowledge_bases`."},"agentId":{"type":"string","description":"Agent used when source is `agent`."},"mode":{"type":"string","description":"How contextual-knowledge answers are surfaced.","enum":["suggest_action","automatic_answer"]}},"required":["mode","source"]}},"required":["contextualKnowledgeAnswers"]},{"type":"object","title":"signals","properties":{"signals":{"type":"object","description":"Configuration for the built-in signals feature, each signal answered by a decision model. The\n catalog has three groups:\n\n - `moments`: moments that need someone now, such as an escalation request, a legal threat or a\n repeat contact. Asked when a customer turn ends.\n - `safety`: abuse or threats toward the agent, and hate or discriminatory language. Asked when\n a customer turn ends.\n - `sales`: methodology elements (MEDDPICC, BANT), buying signals, the objection raised and its\n type, and deal risks. Asked once the `sales` or `deal_process` topic category is detected, so\n a conversation that is not about buying never asks them.\n\n The company's own signals are intents. Each signal of an included group counts towards the\n session's question limit.","properties":{"include":{"type":"array","items":{"type":"string","maxLength":64,"minLength":1,"pattern":"^[a-z0-9](?:[a-z0-9_]*[a-z0-9])?$","description":"A name from a built-in catalog: a group (a topic category, a signal group) or a single label\n in one. The catalog lives with the service, which rejects a name it does not know, so new groups\n and labels need no SDK release. A company's own topic categories and topics use the same form."},"maxItems":256,"description":"Groups or single signals to detect. Empty or absent means every group."}}}},"required":["signals"]}]},"description":"Copilot features enabled for this profile. Each list item is a typed feature and its configuration."},"assets":{"type":"array","items":{"type":"string","maxLength":128,"minLength":1},"description":"Assets referenced by this profile."},"intents":{"type":"array","items":{"type":"string","maxLength":128,"minLength":1},"description":"Intents referenced by this profile."},"evaluations":{"type":"array","items":{"type":"string","maxLength":128,"minLength":1},"description":"Evaluations referenced by this profile."},"automations":{"type":"array","items":{"type":"string","maxLength":128,"minLength":1},"description":"Automations referenced by this profile."}},"required":["name"]}}}},"/v2/copilot/profiles/{profileId}":{"get":{"operationId":"GetProfile","summary":"","description":"Gets a profile draft, including metadata, referenced library resource ids and version state.\n\n### Allowed Auth Scopes\n\n- wilma-copilot:GetProfile\n- wilma-copilot:read","parameters":[{"name":"profileId","in":"path","required":true,"schema":{"type":"string","maxLength":128,"minLength":1}},{"name":"company","in":"query","required":false,"schema":{"type":"string","maxLength":30,"minLength":3}}]},"put":{"operationId":"UpdateProfile","summary":"","description":"Updates profile metadata only. Attachments are changed through AttachProfileResource and DeleteProfileResource. Does not affect live conversations until the draft is published.\n\n### Allowed Auth Scopes\n\n- wilma-copilot:UpdateProfile","parameters":[{"name":"profileId","in":"path","required":true,"schema":{"type":"string","maxLength":128,"minLength":1}},{"name":"company","in":"query","required":false,"schema":{"type":"string","maxLength":30,"minLength":3}}],"requestBody":{"required":true,"schema":{"type":"object","description":"Profile fields shared by the editable draft and the published snapshot.","properties":{"name":{"type":"string"},"description":{"type":"string"},"audience":{"type":"array","items":{"type":"object","description":"One audience entry: a kind, and the values of that kind the profile assists. An entry with no values does not narrow anything.","properties":{"kind":{"type":"string","description":"The kind of thing a profile is aimed at.\n\n Two families, and the difference decides which stages the entry can constrain.\n `user`, `group`, `department` and `agent` name **who is assisted** - a person for the first\n three, a Wilma AI agent for the last - and in the `before` stage the assisted party is\n whoever opened copilot, so they constrain every stage. `queue`, `service` and `trunk`\n describe a **live conversation**, which does not exist in `before` - so an entry of those\n kinds says nothing there.\n\n Values are the tenant's own routing objects: user emails for `user`, Wilma agent ids for\n `agent`, PBX group and department ids, queue and trunk names as `wda-stream` reports them,\n and x-bees service uris for `service`.","enum":["user","group","department","agent","queue","service","trunk"]},"values":{"type":"array","items":{"type":"string"}}},"required":["kind","values"]},"description":"Who this profile assists - queues, groups, departments, users, AI agents, chat services\n and trunks. Empty or absent means everyone in the company. This is the profile's primary\n scope: an administrator reaches for \"my refunds team\", not for a conversation medium,\n and a queue already carries the medium, the skill and the line of business."},"stages":{"type":"array","items":{"type":"string","description":"Where in a conversation's life a copilot resource applies.\n\n `before` is the pre-conversation session copilot opens from a client lookup (`Open` in\n `wildix.wilma.copilot.sessions`): a customer is on screen and no call, conference or chat\n exists yet. `during` is a live conversation. `after` is everything from the conversation\n ending onwards - wrap-up, and the post-conversation summary that carries CSAT and topics.\n\n A conversation that has not started has no channel and no queue, which is the one rule the\n other two members of this triple follow from: see `CopilotChannelList` and\n `CopilotAudienceKind`.","enum":["before","during","after"]},"description":"Stages of a conversation this profile applies in. Empty or absent means every stage."},"channels":{"type":"array","items":{"type":"string","description":"The kind of conversation a copilot resource applies to.\n\n Singular members and no wildcard, unlike the `calls | chats | any` field this replaces -\n and `conference` is nameable for the first time. Note that this is the *conversation*\n channel, not the Wilma runtime channel (`assistant` / `chat` / `voice`) and not the\n messaging service a chat arrived on (which is `chat.service`).","enum":["call","conference","chat"]},"description":"Conversation channels this profile applies to. Empty or absent means every channel.\n\n Only constrains the `during` and `after` stages - nothing is a call, a conference or a\n chat before it starts - so a profile whose `stages` are `before` alone applies whatever\n this says."},"conditions":{"deprecated":true,"description":"Conditions under which the profile matches a conversation. An empty tree always matches. Deprecated - see the deprecation note.\nThis shape is deprecated since 2026-08: Profile conditions are resolved once, on the session's first event, so a rule over anything copilot has yet to detect never activates the profile. Use `audience` / `stages` / `channels` for what is known at that moment, and put everything conditional on the assets, intents and automations the profile carries, where conditions are re-evaluated as state changes. Still read for profiles authored before those members existed."},"revision":{"type":"number","description":"Draft revision this update is based on (optimistic locking). When it does not match the current revision, RevisionConflictException is returned."}},"required":["name","revision"]}}},"delete":{"operationId":"DeleteProfile","summary":"","description":"Deletes a profile and its version history. Library resources referenced by the profile are not deleted.\n\n### Allowed Auth Scopes\n\n- wilma-copilot:DeleteProfile","parameters":[{"name":"profileId","in":"path","required":true,"schema":{"type":"string","maxLength":128,"minLength":1}},{"name":"company","in":"query","required":false,"schema":{"type":"string","maxLength":30,"minLength":3}}]}},"/v2/copilot/profiles/{profileId}/compare-versions":{"get":{"operationId":"CompareProfileVersions","summary":"","description":"Returns the field-level differences between two versions, or between a version and the current draft.\n\n### Allowed Auth Scopes\n\n- wilma-copilot:CompareProfileVersions\n- wilma-copilot:read","parameters":[{"name":"profileId","in":"path","required":true,"schema":{"type":"string","maxLength":128,"minLength":1}},{"name":"company","in":"query","required":false,"schema":{"type":"string","maxLength":30,"minLength":3}},{"name":"from","in":"query","required":false,"schema":{"type":"number","minimum":1,"description":"Base published version number. Omit when fromDraft is true."},"description":"Base published version number. Omit when fromDraft is true."},{"name":"fromDraft","in":"query","required":false,"schema":{"type":"boolean","description":"Compare against the current draft instead of a published version."},"description":"Compare against the current draft instead of a published version."},{"name":"to","in":"query","required":false,"schema":{"type":"number","minimum":1,"description":"Target published version number. Omit when toDraft is true."},"description":"Target published version number. Omit when toDraft is true."},{"name":"toDraft","in":"query","required":false,"schema":{"type":"boolean","description":"Compare against the current draft instead of a published version."},"description":"Compare against the current draft instead of a published version."}]}},"/v2/copilot/profiles/{profileId}/disable":{"patch":{"operationId":"DisableProfile","summary":"","description":"Disables a profile so it is no longer applied to live conversations. Its draft and published versions are kept.\n\n### Allowed Auth Scopes\n\n- wilma-copilot:DisableProfile","parameters":[{"name":"profileId","in":"path","required":true,"schema":{"type":"string","maxLength":128,"minLength":1}},{"name":"company","in":"query","required":false,"schema":{"type":"string","maxLength":30,"minLength":3}}]}},"/v2/copilot/profiles/{profileId}/enable":{"patch":{"operationId":"EnableProfile","summary":"","description":"Enables a published profile so its latest version is applied to live conversations.\n\n### Allowed Auth Scopes\n\n- wilma-copilot:EnableProfile","parameters":[{"name":"profileId","in":"path","required":true,"schema":{"type":"string","maxLength":128,"minLength":1}},{"name":"company","in":"query","required":false,"schema":{"type":"string","maxLength":30,"minLength":3}}]}},"/v2/copilot/profiles/{profileId}/resources":{"post":{"operationId":"AttachProfileResource","summary":"","description":"Attaches one feature or reusable library resource to the profile draft. Attaching an already attached resource is idempotent; an existing feature of the same type is updated with the supplied configuration. Does not affect live conversations until the draft is published.\n\n### Allowed Auth Scopes\n\n- wilma-copilot:AttachProfileResource","parameters":[{"name":"profileId","in":"path","required":true,"schema":{"type":"string","maxLength":128,"minLength":1}},{"name":"company","in":"query","required":false,"schema":{"type":"string","maxLength":30,"minLength":3}}],"requestBody":{"required":true,"schema":{"type":"object","properties":{"revision":{"type":"number","description":"Draft revision this update is based on (optimistic locking)."},"resource":{"description":"One feature or reusable library resource attached to a profile.","oneOf":[{"type":"object","title":"feature","properties":{"feature":{"description":"A Copilot feature enabled for a profile, with its type-specific configuration.","oneOf":[{"type":"object","title":"csat","properties":{"csat":{"type":"object","description":"Configuration for the built-in CSAT feature."}},"required":["csat"]},{"type":"object","title":"sentiment","properties":{"sentiment":{"type":"object","description":"Configuration for the built-in sentiment feature."}},"required":["sentiment"]},{"type":"object","title":"talkRatio","properties":{"talkRatio":{"type":"object","description":"Configuration for the built-in talk ratio feature."}},"required":["talkRatio"]},{"type":"object","title":"topics","properties":{"topics":{"type":"object","description":"Configuration for the built-in topics feature.\n\n Topics are classified, not summarised: a decision model answers, for each category and topic,\n whether the conversation covers it, and `topics` holds the names of the categories and topics it\n does - values a condition can compare. A category is asked on every full-transcript pass until it\n is detected; its topics are asked once it is. A category's name is in `topics` when the category\n itself is detected, not when one of its topics is.\n\n `custom` adds the company's own: new categories with their topics, and topics added to a\n built-in category, such as `mastercard` in `payment`. Each category and topic, built-in or\n custom, counts towards the session's question limit.","properties":{"include":{"type":"array","items":{"type":"string","maxLength":64,"minLength":1,"pattern":"^[a-z0-9](?:[a-z0-9_]*[a-z0-9])?$","description":"A name from a built-in catalog: a group (a topic category, a signal group) or a single label\n in one. The catalog lives with the service, which rejects a name it does not know, so new groups\n and labels need no SDK release. A company's own topic categories and topics use the same form."},"maxItems":256,"description":"Built-in categories, or single topics in them, to classify against. Empty or absent means every category."},"custom":{"type":"array","items":{"type":"object","description":"A topic category of the company's own, or the company's topics in a built-in category. A new\n category is asked like a built-in one, and its topics once it is detected.","properties":{"name":{"type":"string","maxLength":64,"minLength":1,"pattern":"^[a-z0-9](?:[a-z0-9_]*[a-z0-9])?$","description":"A new category's name, or the name of the built-in category its topics are added to."},"description":{"type":"string","maxLength":500,"minLength":1,"description":"What a new category covers, as the decision model reads it: required for a new category,\n and left out when extending a built-in one, which keeps its own."},"topics":{"type":"array","items":{"type":"object","description":"A topic of the company's own, in the category that holds it.","properties":{"name":{"type":"string","maxLength":64,"minLength":1,"pattern":"^[a-z0-9](?:[a-z0-9_]*[a-z0-9])?$","description":"The topic's name in `topics` and in conditions; not a name the catalog already uses."},"description":{"type":"string","maxLength":500,"minLength":1,"description":"What the conversation covers when this topic applies, as the decision model reads it, e.g. 'Customer pays, or asks about paying, by Mastercard'."}},"required":["description","name"]},"maxItems":64,"minItems":1}},"required":["name","topics"]},"maxItems":32,"description":"The company's own categories, and the topics it adds to built-in ones; at most 64 topics in\n all. A name is used once across `custom`, and only a category that extends a built-in one\n uses a catalog name. Topics added to a built-in category are asked like its own: once it is\n detected when the whole category is included, from the start when only some of its topics\n are. A built-in category that `include` leaves out entirely cannot be extended."}}}},"required":["topics"]},{"type":"object","title":"contextualKnowledgeAnswers","properties":{"contextualKnowledgeAnswers":{"type":"object","description":"Configuration for the built-in contextual knowledge answers feature.","properties":{"source":{"type":"string","description":"Where contextual-knowledge answers are sourced from.","enum":["knowledge_bases","agent"]},"knowledgeBaseIds":{"type":"array","items":{"type":"string","description":"Id of a knowledge base referenced by copilot. Managed in the wim-knowledge-base service."},"description":"Knowledge bases used when source is `knowledge_bases`."},"agentId":{"type":"string","description":"Agent used when source is `agent`."},"mode":{"type":"string","description":"How contextual-knowledge answers are surfaced.","enum":["suggest_action","automatic_answer"]}},"required":["mode","source"]}},"required":["contextualKnowledgeAnswers"]},{"type":"object","title":"signals","properties":{"signals":{"type":"object","description":"Configuration for the built-in signals feature, each signal answered by a decision model. The\n catalog has three groups:\n\n - `moments`: moments that need someone now, such as an escalation request, a legal threat or a\n repeat contact. Asked when a customer turn ends.\n - `safety`: abuse or threats toward the agent, and hate or discriminatory language. Asked when\n a customer turn ends.\n - `sales`: methodology elements (MEDDPICC, BANT), buying signals, the objection raised and its\n type, and deal risks. Asked once the `sales` or `deal_process` topic category is detected, so\n a conversation that is not about buying never asks them.\n\n The company's own signals are intents. Each signal of an included group counts towards the\n session's question limit.","properties":{"include":{"type":"array","items":{"type":"string","maxLength":64,"minLength":1,"pattern":"^[a-z0-9](?:[a-z0-9_]*[a-z0-9])?$","description":"A name from a built-in catalog: a group (a topic category, a signal group) or a single label\n in one. The catalog lives with the service, which rejects a name it does not know, so new groups\n and labels need no SDK release. A company's own topic categories and topics use the same form."},"maxItems":256,"description":"Groups or single signals to detect. Empty or absent means every group."}}}},"required":["signals"]}]}},"required":["feature"]},{"type":"object","title":"asset","properties":{"asset":{"type":"string","maxLength":128,"minLength":1}},"required":["asset"]},{"type":"object","title":"automation","properties":{"automation":{"type":"string","maxLength":128,"minLength":1}},"required":["automation"]},{"type":"object","title":"intent","properties":{"intent":{"type":"string","maxLength":128,"minLength":1}},"required":["intent"]},{"type":"object","title":"evaluation","properties":{"evaluation":{"type":"string","maxLength":128,"minLength":1}},"required":["evaluation"]}]}},"required":["resource","revision"]}}}},"/v2/copilot/profiles/{profileId}/resources/{resourceType}/{resourceId}":{"delete":{"operationId":"DeleteProfileResource","summary":"","description":"Deletes one feature or reusable library-resource attachment from the profile draft. Deleting an already absent resource is idempotent. Does not affect live conversations until the draft is published.\n\n### Allowed Auth Scopes\n\n- wilma-copilot:DeleteProfileResource","parameters":[{"name":"profileId","in":"path","required":true,"schema":{"type":"string","maxLength":128,"minLength":1}},{"name":"resourceType","in":"path","required":true,"schema":{"type":"string","description":"The kind of resource attached to a profile.","enum":["feature","asset","automation","intent","evaluation"]},"description":"The kind of resource attached to a profile."},{"name":"resourceId","in":"path","required":true,"schema":{"type":"string","maxLength":128,"minLength":1,"description":"Identifier of an attached profile resource. For a feature, use the feature union member name, such as `csat` or `sentiment`."},"description":"Identifier of an attached profile resource. For a feature, use the feature union member name, such as `csat` or `sentiment`."},{"name":"company","in":"query","required":false,"schema":{"type":"string","maxLength":30,"minLength":3}},{"name":"revision","in":"query","required":true,"schema":{"type":"number","description":"Draft revision this update is based on (optimistic locking)."},"description":"Draft revision this update is based on (optimistic locking)."}]}},"/v2/copilot/profiles/{profileId}/versions":{"get":{"operationId":"ListProfileVersions","summary":"","description":"Lists the published versions of a profile, newest first.\n\n### Allowed Auth Scopes\n\n- wilma-copilot:ListProfileVersions\n- wilma-copilot:read","parameters":[{"name":"profileId","in":"path","required":true,"schema":{"type":"string","maxLength":128,"minLength":1}},{"name":"company","in":"query","required":false,"schema":{"type":"string","maxLength":30,"minLength":3}},{"name":"limit","in":"query","required":false,"schema":{"type":"number","maximum":100,"minimum":1}},{"name":"cursor","in":"query","required":false,"schema":{"type":"string","maxLength":2048,"minLength":1,"description":"Opaque cursor returned by a previous page; pass it back to fetch the next page."},"description":"Opaque cursor returned by a previous page; pass it back to fetch the next page."}]},"post":{"operationId":"PublishProfileVersion","summary":"","description":"Publishes the current draft as a new immutable version and updates latestVersion. The referenced intents, evaluations, automations and assets are resolved into the version snapshot, and the wilma-copilot-sessions runtime applies the new version to conversations started after publish.\n\n### Allowed Auth Scopes\n\n- wilma-copilot:PublishProfileVersion","parameters":[{"name":"profileId","in":"path","required":true,"schema":{"type":"string","maxLength":128,"minLength":1}},{"name":"company","in":"query","required":false,"schema":{"type":"string","maxLength":30,"minLength":3}}],"requestBody":{"required":true,"schema":{"type":"object","properties":{"changelog":{"type":"string","maxLength":500,"minLength":1,"description":"What changed in this version."},"revision":{"type":"number","description":"Draft revision this publish is based on (optimistic locking)."}},"required":["revision"]}}}},"/v2/copilot/profiles/{profileId}/versions/{version}":{"get":{"operationId":"GetProfileVersion","summary":"","description":"Retrieves a published profile version, including its full immutable definition snapshot with resolved library resources.\n\n### Allowed Auth Scopes\n\n- wilma-copilot:GetProfileVersion\n- wilma-copilot:read","parameters":[{"name":"profileId","in":"path","required":true,"schema":{"type":"string","maxLength":128,"minLength":1}},{"name":"version","in":"path","required":true,"schema":{"type":"number","minimum":1,"description":"Published profile version number. Monotonic within a profile's version history, starting at 1."},"description":"Published profile version number. Monotonic within a profile's version history, starting at 1."},{"name":"company","in":"query","required":false,"schema":{"type":"string","maxLength":30,"minLength":3}}]}},"/v2/copilot/profiles/{profileId}/versions/{version}/draft":{"post":{"operationId":"RestoreProfileVersionToDraft","summary":"","description":"Replaces the mutable draft with a published version's definition. The draft revision is incremented; published versions and latestVersion are unchanged until the draft is published.\n\n### Allowed Auth Scopes\n\n- wilma-copilot:RestoreProfileVersionToDraft","parameters":[{"name":"profileId","in":"path","required":true,"schema":{"type":"string","maxLength":128,"minLength":1}},{"name":"version","in":"path","required":true,"schema":{"type":"number","minimum":1,"description":"Published profile version number. Monotonic within a profile's version history, starting at 1."},"description":"Published profile version number. Monotonic within a profile's version history, starting at 1."},{"name":"company","in":"query","required":false,"schema":{"type":"string","maxLength":30,"minLength":3}}],"requestBody":{"required":true,"schema":{"type":"object","properties":{"revision":{"type":"number","description":"Draft revision this restore is based on (optimistic locking)."}},"required":["revision"]}}}},"/v2/copilot/enrich":{"post":{"operationId":"Enrich","summary":"","description":"Attaches late-arriving lookup evidence to a relationship — e.g. a CRM match the iframe\n resolved after Open, or a human wrong-match correction. `relationshipId` is the\n client's current belief, not an instruction: evidence runs through the same resolution rules,\n and when it proves the customer is someone else (e.g. a human-confirmed entity bound\n elsewhere), the response returns the authoritative relationship — which may differ from the\n input. Fresh values refresh `contact.*` / `crm.*` state in the relationship's active sessions\n and re-evaluate conditions, so assets can appear mid-call the moment a match lands.\n\n### Allowed Auth Scopes\n\n- wilma-copilot-sessions:Enrich\n- wilma-copilot-sessions:write","parameters":[],"requestBody":{"required":true,"schema":{"type":"object","properties":{"company":{"type":"string","maxLength":30,"minLength":3,"description":"Company that owns the relationship. Required when calling with a service (s2s) token."},"relationshipId":{"type":"string","maxLength":128,"minLength":1,"description":"Id of a relationship: the durable, cross-channel view of one customer that copilot sessions attach to. Identities (emails, phones, entities) merge into it as context is ingested."},"lookup":{"type":"object","description":"The result of a client-side contact lookup: the identities and enrichment in scope for the customer. Reused by Open (optional — an already-bound channel or an anonymous call needs none) and Enrich (late-arriving evidence, e.g. a CRM match resolved after open).","properties":{"emails":{"type":"array","items":{"type":"string"},"description":"Customer email addresses. Identity, first-class: merged into the relationship."},"phones":{"type":"array","items":{"type":"string"},"description":"Customer phone numbers. Identity, first-class: merged into the relationship."},"entities":{"type":"array","items":{"type":"object","description":"A typed reference to a record in an external system. `type` is dot-namespaced by system, e.g. `salesforce.contact`, `salesforce.account`, `hubspot.contact`, `phonebook.contact`.","properties":{"id":{"type":"string"},"type":{"type":"string"}},"required":["id","type"]},"description":"Typed references to records in external systems, e.g. a Salesforce contact. Identity, first-class: merged into the relationship."},"attributes":{"type":"object","additionalProperties":{"type":"string"},"propertyNames":{"type":"string"},"description":"Open string-to-string enrichment carried by a context event, e.g. `phonebook.name`, `call.direction`, `salesforce.deal_stage`, `match.confidence`. Keys are dot-namespaced by source. Values feed the session state that profile conditions evaluate. Identities do not belong here — emails, phones and entity references are first-class envelope fields."}}}},"required":["lookup","relationshipId"]}}}},"/v2/copilot/lookup":{"post":{"operationId":"Resolve","summary":"","description":"Resolves a lookup to a relationship and its sessions without opening copilot or creating\n anything — a read-only peek. Use it to decide whether a customer has history before opening,\n e.g. to badge the Wilma button. Returns no relationship when nothing matches; relationships\n are only created by Open.\n\n### Allowed Auth Scopes\n\n- wilma-copilot-sessions:Resolve\n- wilma-copilot-sessions:read","parameters":[],"requestBody":{"required":true,"schema":{"type":"object","properties":{"company":{"type":"string","maxLength":30,"minLength":3,"description":"Company scope. Required when calling with a service (s2s) token."},"lookup":{"type":"object","description":"The result of a client-side contact lookup: the identities and enrichment in scope for the customer. Reused by Open (optional — an already-bound channel or an anonymous call needs none) and Enrich (late-arriving evidence, e.g. a CRM match resolved after open).","properties":{"emails":{"type":"array","items":{"type":"string"},"description":"Customer email addresses. Identity, first-class: merged into the relationship."},"phones":{"type":"array","items":{"type":"string"},"description":"Customer phone numbers. Identity, first-class: merged into the relationship."},"entities":{"type":"array","items":{"type":"object","description":"A typed reference to a record in an external system. `type` is dot-namespaced by system, e.g. `salesforce.contact`, `salesforce.account`, `hubspot.contact`, `phonebook.contact`.","properties":{"id":{"type":"string"},"type":{"type":"string"}},"required":["id","type"]},"description":"Typed references to records in external systems, e.g. a Salesforce contact. Identity, first-class: merged into the relationship."},"attributes":{"type":"object","additionalProperties":{"type":"string"},"propertyNames":{"type":"string"},"description":"Open string-to-string enrichment carried by a context event, e.g. `phonebook.name`, `call.direction`, `salesforce.deal_stage`, `match.confidence`. Keys are dot-namespaced by source. Values feed the session state that profile conditions evaluate. Identities do not belong here — emails, phones and entity references are first-class envelope fields."}}}},"required":["lookup"]}}}},"/v2/copilot/open":{"post":{"operationId":"Open","summary":"","description":"Opens copilot for a client surface. `lookup` (when present) is treated as hints: emails,\n phones and entity references are resolved authoritatively server-side and merged into a\n relationship. Routing picks the session — clients never pass conversation ids: the\n relationship's live conversation session (created automatically when a call or chat starts)\n when one exists, otherwise the relationship's `app` session (server-minted, reused while\n idle). The response returns the sessions already associated with the relationship, so the client\n can render cross-channel history without a second call.\n\n### Allowed Auth Scopes\n\n- wilma-copilot-sessions:Open\n- wilma-copilot-sessions:write","parameters":[],"requestBody":{"required":false,"schema":{"type":"object","properties":{"company":{"type":"string","maxLength":30,"minLength":3,"description":"Company that owns the relationship. Required when calling with a service (s2s) token, which is not scoped to a single company."},"lookup":{"type":"object","description":"The result of a client-side contact lookup: the identities and enrichment in scope for the customer. Reused by Open (optional — an already-bound channel or an anonymous call needs none) and Enrich (late-arriving evidence, e.g. a CRM match resolved after open).","properties":{"emails":{"type":"array","items":{"type":"string"},"description":"Customer email addresses. Identity, first-class: merged into the relationship."},"phones":{"type":"array","items":{"type":"string"},"description":"Customer phone numbers. Identity, first-class: merged into the relationship."},"entities":{"type":"array","items":{"type":"object","description":"A typed reference to a record in an external system. `type` is dot-namespaced by system, e.g. `salesforce.contact`, `salesforce.account`, `hubspot.contact`, `phonebook.contact`.","properties":{"id":{"type":"string"},"type":{"type":"string"}},"required":["id","type"]},"description":"Typed references to records in external systems, e.g. a Salesforce contact. Identity, first-class: merged into the relationship."},"attributes":{"type":"object","additionalProperties":{"type":"string"},"propertyNames":{"type":"string"},"description":"Open string-to-string enrichment carried by a context event, e.g. `phonebook.name`, `call.direction`, `salesforce.deal_stage`, `match.confidence`. Keys are dot-namespaced by source. Values feed the session state that profile conditions evaluate. Identities do not belong here — emails, phones and entity references are first-class envelope fields."}}}}}}}},"/v2/copilot/relationships/{relationshipId}/sessions":{"get":{"operationId":"ListRelationshipSessions","summary":"","description":"Lists the copilot sessions associated with a relationship, most recent first — the paginated\n form of the `related` / `sessions` lists Open, Enrich and Resolve return inline. Use it to page\n a customer's cross-channel history, and to refresh after a `relationship` stream event announces\n a new session. Sessions are company-owned and shared within the tenant; an unknown relationship\n in scope returns an empty list rather than an error.\n\n### Allowed Auth Scopes\n\n- wilma-copilot-sessions:ListRelationshipSessions\n- wilma-copilot-sessions:read","parameters":[{"name":"relationshipId","in":"path","required":true,"schema":{"type":"string","maxLength":128,"minLength":1,"description":"The relationship whose sessions to list, as returned by Open, Enrich or Resolve."},"description":"The relationship whose sessions to list, as returned by Open, Enrich or Resolve."},{"name":"company","in":"query","required":false,"schema":{"type":"string","maxLength":30,"minLength":3,"description":"Company that owns the relationship. Required when calling with a service (s2s) token, which is not scoped to a single company."},"description":"Company that owns the relationship. Required when calling with a service (s2s) token, which is not scoped to a single company."},{"name":"limit","in":"query","required":false,"schema":{"type":"number","maximum":100,"minimum":1}},{"name":"cursor","in":"query","required":false,"schema":{"type":"string","maxLength":2048,"minLength":1,"description":"Opaque cursor returned by a previous page; pass it back to fetch the next page."},"description":"Opaque cursor returned by a previous page; pass it back to fetch the next page."}]}},"/v2/copilot/sessions":{"get":{"operationId":"copilot-sessions.ListSessions","summary":"","description":"Lists copilot sessions a user participated in, most recent first. Lets a user find a session they opened earlier without knowing the relationship.\n\n### Allowed Auth Scopes\n\n- wilma-copilot-sessions:ListSessions\n- wilma-copilot-sessions:read","parameters":[{"name":"company","in":"query","required":false,"schema":{"type":"string","maxLength":30,"minLength":3,"description":"Company scope. Required when calling with a service (s2s) token."},"description":"Company scope. Required when calling with a service (s2s) token."},{"name":"user","in":"query","required":false,"schema":{"type":"string","maxLength":12,"minLength":12,"description":"The user whose participated sessions to list. Defaults to the caller; required when calling with a service (s2s) token."},"description":"The user whose participated sessions to list. Defaults to the caller; required when calling with a service (s2s) token."},{"name":"limit","in":"query","required":false,"schema":{"type":"number","maximum":100,"minimum":1}},{"name":"cursor","in":"query","required":false,"schema":{"type":"string","maxLength":2048,"minLength":1,"description":"Opaque cursor returned by a previous page; pass it back to fetch the next page."},"description":"Opaque cursor returned by a previous page; pass it back to fetch the next page."}]}},"/v2/copilot/sessions/{sessionId}/actions":{"post":{"operationId":"Act","summary":"","description":"Submits one user interaction with copilot output — a knowledge-search suggestion click, a\n smart-action invocation, or playbook progress the agent made by hand. The action is appended to\n the session's input mailbox and processed asynchronously by the session worker; outcomes surface\n in the event log as `action` events and asset updates. Clicks also feed the per-resource usage\n statistics in the wilma-copilot config API.\n\n### Allowed Auth Scopes\n\n- wilma-copilot-sessions:Act\n- wilma-copilot-sessions:write","parameters":[{"name":"sessionId","in":"path","required":true,"schema":{"type":"string","maxLength":128,"minLength":1}},{"name":"company","in":"query","required":false,"schema":{"type":"string","maxLength":30,"minLength":3,"description":"Company that owns the session. Required when calling with a service (s2s) token, which is not scoped to a single company."},"description":"Company that owns the session. Required when calling with a service (s2s) token, which is not scoped to a single company."}],"requestBody":{"required":true,"schema":{"type":"object","properties":{"action":{"description":"One user interaction with copilot output. Exactly one member is set.","oneOf":[{"type":"object","title":"knowledgeSearch","properties":{"knowledgeSearch":{"type":"object","description":"Launches the knowledge search suggested for a detected question (contextual knowledge answers, `suggest_action` mode).","properties":{"questionId":{"type":"string","description":"The suggested question's id — the `questionId` carried by the suggestion asset's action payload."},"question":{"type":"string","description":"The question text; used when the suggestion is no longer part of the live session state (e.g. after a revive)."}},"required":["questionId"]}},"required":["knowledgeSearch"]},{"type":"object","title":"smartAction","properties":{"smartAction":{"type":"object","description":"Invokes one smart action offered by an asset.","properties":{"assetId":{"type":"string","description":"Id of the asset carrying the smart action."},"actionId":{"type":"string","description":"Id of the clicked action inside the asset's smart-action list."},"variables":{"description":"Values for `guided` variable bindings collected from the user, keyed by variable name."}},"required":["actionId","assetId"]}},"required":["smartAction"]},{"type":"object","title":"playbook","properties":{"playbook":{"type":"object","description":"Records playbook progress the agent made by hand: the steps that do not auto-complete from the\n transcript, and — where the playbook allows it — the playbook itself. Progress is append-only, so\n a step the transcript already completed stays complete and re-sending an id is a no-op.","properties":{"assetId":{"type":"string","description":"Id of the asset carrying the playbook."},"stepIds":{"type":"array","items":{"type":"string"},"description":"Steps the agent ticked. Ids that are not steps of this playbook are ignored."},"complete":{"type":"boolean","description":"Marks the whole playbook complete. Honoured only when the playbook's `completionCriteria` is\n `manual` with `allowAgentMarkComplete`; ignored for semantic criteria, where the model owns the\n goal verdict. Required steps still gate the checkmark either way."}},"required":["assetId"]}},"required":["playbook"]}]}},"required":["action"]}}}},"/v2/copilot/sessions/{sessionId}/ask":{"post":{"operationId":"Ask","summary":"","description":"Records an ask in a copilot session and publishes it as an `ask` event on the session\n stream. The ask itself runs as a regular Wilma session (wilma-assistant): `wilmaSessionId`\n points at it and `accessToken` is a read-only, session-scoped token other agents present to\n wilma-assistant to render the ask output — copilot sessions are shared within the tenant, so\n any agent watching the stream may view it.\n\n### Allowed Auth Scopes\n\n- wilma-copilot-sessions:Ask\n- wilma-copilot-sessions:write","parameters":[{"name":"sessionId","in":"path","required":true,"schema":{"type":"string","maxLength":128,"minLength":1,"description":"The copilot session to record the ask in."},"description":"The copilot session to record the ask in."}],"requestBody":{"required":true,"schema":{"type":"object","properties":{"company":{"type":"string","maxLength":30,"minLength":3,"description":"Company that owns the session. Required when calling with a service (s2s) token."},"wilmaSessionId":{"type":"string","description":"The Wilma session (wilma-assistant) holding the ask and its output."},"accessToken":{"type":"string","description":"Read-only, expiring token scoped to `wilmaSessionId`, issued by wilma-assistant. Published on the `ask` event so other agents can view the output."}},"required":["wilmaSessionId"]}}}},"/v2/copilot/sessions/{sessionId}/events":{"get":{"operationId":"copilot-sessions.GetSessionEvents","summary":"","description":"Returns recorded copilot session events as a plain JSON page, without streaming. Use it to inspect past sessions or to catch up before subscribing to StreamSessionEvents.\n\n### Allowed Auth Scopes\n\n- wilma-copilot-sessions:GetSessionEvents\n- wilma-copilot-sessions:read","parameters":[{"name":"sessionId","in":"path","required":true,"schema":{"type":"string","maxLength":128,"minLength":1}},{"name":"company","in":"query","required":false,"schema":{"type":"string","maxLength":30,"minLength":3,"description":"Company that owns the session. Required when calling with a service (s2s) token, which is not scoped to a single company."},"description":"Company that owns the session. Required when calling with a service (s2s) token, which is not scoped to a single company."},{"name":"startIndex","in":"query","required":false,"schema":{"type":"number","minimum":0,"description":"Return events with `index >= startIndex`. Defaults to 0."},"description":"Return events with `index >= startIndex`. Defaults to 0."},{"name":"limit","in":"query","required":false,"schema":{"type":"number","maximum":1000,"minimum":1,"description":"Maximum number of events to return. Defaults to 1000."},"description":"Maximum number of events to return. Defaults to 1000."}]}},"/v2/copilot/sessions/{sessionId}/events/stream":{"get":{"operationId":"copilot-sessions.StreamSessionEvents","summary":"","description":"Replays and tails copilot session events as Server-Sent Events (`text/event-stream`). Each frame carries one CopilotSessionEvent: `id:` is the event index, `event:` is the union member name, and `data:` is the JSON document of the union value. The stream closes after a `lifecycle` event that leaves the session dormant (`idle`, `archived`) or `failed`; reconnect with the last seen index + 1 as `startIndex` to resume, including after a dormant session revives. `startIndex` is the only resume cursor — no `Last-Event-ID` header is read, because a client already tracks the index it is rendering, and that is the same cursor GetSessionEvents pages with.\n\n### Allowed Auth Scopes\n\n- wilma-copilot-sessions:StreamSessionEvents\n- wilma-copilot-sessions:read","parameters":[{"name":"sessionId","in":"path","required":true,"schema":{"type":"string","maxLength":128,"minLength":1}},{"name":"company","in":"query","required":false,"schema":{"type":"string","maxLength":30,"minLength":3,"description":"Company that owns the session. Required when calling with a service (s2s) token, which is not scoped to a single company."},"description":"Company that owns the session. Required when calling with a service (s2s) token, which is not scoped to a single company."},{"name":"startIndex","in":"query","required":false,"schema":{"type":"number","minimum":0,"description":"Replay recorded events with `index >= startIndex` before tailing live ones. Defaults to 0."},"description":"Replay recorded events with `index >= startIndex` before tailing live ones. Defaults to 0."}]}},"/v2/copilot/sessions/{sessionId}/state":{"get":{"operationId":"GetSessionState","summary":"","description":"Returns the whole state of a copilot session: the conversation and its participants, the full transcript, and every intent, evaluation, asset, action and ask so far, each named. Reflects the events applied so far and never waits for pending ones.\n\n### Allowed Auth Scopes\n\n- wilma-copilot-sessions:GetSessionState\n- wilma-copilot-sessions:read","parameters":[{"name":"sessionId","in":"path","required":true,"schema":{"type":"string","maxLength":128,"minLength":1}},{"name":"company","in":"query","required":false,"schema":{"type":"string","maxLength":30,"minLength":3,"description":"Company that owns the session. Required when calling with a service (s2s) token, which is not scoped to a single company."},"description":"Company that owns the session. Required when calling with a service (s2s) token, which is not scoped to a single company."}]}},"/v2/copilot/sessions/{sessionId}/views":{"post":{"operationId":"Track","summary":"","description":"Reports which copilot assets the user actually saw rendered (impressions). Copilot may emit\n assets nobody sees — the panel can be closed — so emitted counts alone overstate reach. Views\n reported here feed the per-resource emitted / seen / clicked statistics administrators read\n from the wilma-copilot config API. Batch-friendly: report visible assets when they render,\n deduplicated per session by the server.\n\n### Allowed Auth Scopes\n\n- wilma-copilot-sessions:Track\n- wilma-copilot-sessions:write","parameters":[{"name":"sessionId","in":"path","required":true,"schema":{"type":"string","maxLength":128,"minLength":1}},{"name":"company","in":"query","required":false,"schema":{"type":"string","maxLength":30,"minLength":3,"description":"Company that owns the session. Required when calling with a service (s2s) token, which is not scoped to a single company."},"description":"Company that owns the session. Required when calling with a service (s2s) token, which is not scoped to a single company."}],"requestBody":{"required":true,"schema":{"type":"object","properties":{"views":{"type":"array","items":{"type":"object","description":"One asset the user actually saw rendered in the copilot panel.","properties":{"assetId":{"type":"string"},"at":{"type":"string","description":"When the asset became visible. Defaults to the time the report is ingested."}},"required":["assetId"]}}},"required":["views"]}}}},"/v2/sandbox/sandboxes":{"get":{"operationId":"ListSandboxes","summary":"","description":"Lists sandboxes that ran (or are running), newest first. Filter by `snapshotId` to audit every\n sandbox launched from a specific snapshot, or by owner, status or time window to audit usage\n across the company.\n\n### Allowed Auth Scopes\n\n- wilma-sandbox:ListSandboxes\n- wilma-sandbox:read","parameters":[{"name":"company","in":"query","required":false,"schema":{"type":"string","maxLength":30,"minLength":3}},{"name":"snapshotId","in":"query","required":false,"schema":{"type":"string","maxLength":12,"minLength":12,"description":"Only sandboxes launched from this snapshot."},"description":"Only sandboxes launched from this snapshot."},{"name":"owner","in":"query","required":false,"schema":{"type":"string","maxLength":512,"minLength":1,"pattern":"^[a-z][a-z0-9+.-]*://.+$","description":"Only sandboxes that ran for this entity, e.g. 'wilma://agents/UXDT1W9fI3sj'."},"description":"Only sandboxes that ran for this entity, e.g. 'wilma://agents/UXDT1W9fI3sj'."},{"name":"status","in":"query","required":false,"schema":{"type":"string","enum":["starting","running","idle","stopped","expired","failed"]}},{"name":"from","in":"query","required":false,"schema":{"type":"string","description":"Only sandboxes created at or after this time."},"description":"Only sandboxes created at or after this time."},{"name":"to","in":"query","required":false,"schema":{"type":"string","description":"Only sandboxes created at or before this time."},"description":"Only sandboxes created at or before this time."},{"name":"limit","in":"query","required":false,"schema":{"type":"number","maximum":100,"minimum":1}},{"name":"cursor","in":"query","required":false,"schema":{"type":"string","maxLength":2048,"minLength":1,"description":"Opaque cursor returned by a previous page; pass it back to fetch the next page."},"description":"Opaque cursor returned by a previous page; pass it back to fetch the next page."}]}},"/v2/sandbox/sandboxes/{sandboxId}":{"get":{"operationId":"GetSandbox","summary":"","description":"Retrieves a single sandbox with its status, lineage (snapshot/owner) and resource usage.\n\n### Allowed Auth Scopes\n\n- wilma-sandbox:GetSandbox\n- wilma-sandbox:read","parameters":[{"name":"sandboxId","in":"path","required":true,"schema":{"type":"string","maxLength":12,"minLength":12}},{"name":"company","in":"query","required":false,"schema":{"type":"string","maxLength":30,"minLength":3}}]}},"/v2/sandbox/sandboxes/{sandboxId}/terminate":{"post":{"operationId":"TerminateSandbox","summary":"","description":"Forcibly stops a running sandbox. Idempotent: terminating an already-stopped sandbox succeeds. Intended for operators reclaiming resources or killing a misbehaving sandbox.\n\n### Allowed Auth Scopes\n\n- wilma-sandbox:TerminateSandbox","parameters":[{"name":"sandboxId","in":"path","required":true,"schema":{"type":"string","maxLength":12,"minLength":12}},{"name":"company","in":"query","required":false,"schema":{"type":"string","maxLength":30,"minLength":3}}]}},"/v2/sandbox/snapshots":{"get":{"operationId":"ListSnapshots","summary":"","description":"Lists sandbox snapshots for the company, newest first.\n\n### Allowed Auth Scopes\n\n- wilma-sandbox:ListSnapshots\n- wilma-sandbox:read","parameters":[{"name":"company","in":"query","required":false,"schema":{"type":"string","maxLength":30,"minLength":3}},{"name":"status","in":"query","required":false,"schema":{"type":"string","enum":["pending","building","ready","failed"]}},{"name":"search","in":"query","required":false,"schema":{"type":"string","description":"Filter by name or description."},"description":"Filter by name or description."},{"name":"limit","in":"query","required":false,"schema":{"type":"number","maximum":100,"minimum":1}},{"name":"cursor","in":"query","required":false,"schema":{"type":"string","maxLength":2048,"minLength":1,"description":"Opaque cursor returned by a previous page; pass it back to fetch the next page."},"description":"Opaque cursor returned by a previous page; pass it back to fetch the next page."}]},"post":{"operationId":"CreateSnapshot","summary":"","description":"Creates a snapshot and starts building its image asynchronously. Returns immediately with the\n snapshot in PENDING/BUILDING status; poll GetSnapshot (or watch GetSnapshotBuildLogs) until it\n reaches READY or FAILED. The build config can be changed afterwards with UpdateSnapshot, which\n queues a new image under the same snapshot id.\n\n### Allowed Auth Scopes\n\n- wilma-sandbox:CreateSnapshot","parameters":[{"name":"company","in":"query","required":false,"schema":{"type":"string","maxLength":30,"minLength":3}}],"requestBody":{"required":true,"schema":{"type":"object","description":"The complete configuration of a snapshot. Everything except `name` and `description` is the\n build configuration. UpdateSnapshot can change it and queue a new immutable image while\n preserving the snapshot id.","properties":{"name":{"type":"string","maxLength":255,"minLength":1,"pattern":"^[a-zA-Z0-9][a-zA-Z0-9 _-]*$"},"description":{"type":"string","maxLength":1000,"minLength":1},"commands":{"type":"array","items":{"type":"string","maxLength":4000,"minLength":1,"description":"Free-form single shell command."},"description":"Shell commands run in order at build time, on top of the platform runtime."},"env":{"type":"array","items":{"type":"object","description":"Environment variable available during the build and at runtime. Set exactly one of an inline\n `value` or a `secret` reference to a secret in wilma-secrets. `secret` is resolved and injected\n at launch, keeping the credential out of the snapshot image; use it for anything sensitive.","properties":{"name":{"type":"string","maxLength":256,"minLength":1,"pattern":"^[a-zA-Z_][a-zA-Z0-9_]*$"},"value":{"type":"string","description":"Inline plaintext value. Mutually exclusive with `secret`."},"secret":{"type":"object","description":"A reference to a stored secret, held by other services in place of the value.\nResolved server-to-server via GetSecretValue at execution time — always to the current value.","properties":{"id":{"type":"string","maxLength":8,"minLength":8,"pattern":"^[0-9A-Za-z]{8}$","description":"Unique identifier of a secret."}},"required":["id"]}},"required":["name"]},"description":"Environment variables available at build time and to every sandbox."},"size":{"type":"string","description":"Compute size of each sandbox launched from the snapshot. Each level maps to a platform-defined vCPU/memory/disk allocation.","enum":["min","small","large","max"]},"network":{"type":"object","properties":{"access":{"type":"string","description":"Network egress policy for sandboxes launched from the snapshot.","enum":["allow-all","deny-all","allowlist"]},"allowedDomains":{"type":"array","items":{"type":"string","maxLength":253,"minLength":1},"description":"Domains reachable when access = ALLOWLIST."}}}},"required":["name"]}}}},"/v2/sandbox/snapshots/{snapshotId}":{"get":{"operationId":"GetSnapshot","summary":"","description":"Retrieves a snapshot, including its full build config, build outcome and runtime usage stats.\n\n### Allowed Auth Scopes\n\n- wilma-sandbox:GetSnapshot\n- wilma-sandbox:read","parameters":[{"name":"snapshotId","in":"path","required":true,"schema":{"type":"string","maxLength":12,"minLength":12,"description":"Stable identifier of a snapshot. Agents reference this id in their sandbox capability."},"description":"Stable identifier of a snapshot. Agents reference this id in their sandbox capability."},{"name":"company","in":"query","required":false,"schema":{"type":"string","maxLength":30,"minLength":3}}]},"put":{"operationId":"UpdateSnapshot","summary":"","description":"Updates a snapshot under its existing id. Changed build settings queue a new immutable image; omitted build fields are preserved, and empty commands or env lists clear them. Pending/building snapshots reject updates. The last successful image remains available until another build succeeds; existing session workspaces remain pinned to their original image. To rebuild an unchanged configuration, call RebuildSnapshot.\n\n### Allowed Auth Scopes\n\n- wilma-sandbox:UpdateSnapshot","parameters":[{"name":"snapshotId","in":"path","required":true,"schema":{"type":"string","maxLength":12,"minLength":12,"description":"Stable identifier of a snapshot. Agents reference this id in their sandbox capability."},"description":"Stable identifier of a snapshot. Agents reference this id in their sandbox capability."},{"name":"company","in":"query","required":false,"schema":{"type":"string","maxLength":30,"minLength":3}}],"requestBody":{"required":true,"schema":{"type":"object","properties":{"name":{"type":"string","maxLength":255,"minLength":1,"pattern":"^[a-zA-Z0-9][a-zA-Z0-9 _-]*$"},"description":{"type":"string","maxLength":1000,"minLength":1},"commands":{"type":"array","items":{"type":"string","maxLength":4000,"minLength":1,"description":"Free-form single shell command."}},"env":{"type":"array","items":{"type":"object","description":"Environment variable available during the build and at runtime. Set exactly one of an inline\n `value` or a `secret` reference to a secret in wilma-secrets. `secret` is resolved and injected\n at launch, keeping the credential out of the snapshot image; use it for anything sensitive.","properties":{"name":{"type":"string","maxLength":256,"minLength":1,"pattern":"^[a-zA-Z_][a-zA-Z0-9_]*$"},"value":{"type":"string","description":"Inline plaintext value. Mutually exclusive with `secret`."},"secret":{"type":"object","description":"A reference to a stored secret, held by other services in place of the value.\nResolved server-to-server via GetSecretValue at execution time — always to the current value.","properties":{"id":{"type":"string","maxLength":8,"minLength":8,"pattern":"^[0-9A-Za-z]{8}$","description":"Unique identifier of a secret."}},"required":["id"]}},"required":["name"]}},"size":{"type":"string","description":"Compute size of each sandbox launched from the snapshot. Each level maps to a platform-defined vCPU/memory/disk allocation.","enum":["min","small","large","max"]},"network":{"type":"object","properties":{"access":{"type":"string","description":"Network egress policy for sandboxes launched from the snapshot.","enum":["allow-all","deny-all","allowlist"]},"allowedDomains":{"type":"array","items":{"type":"string","maxLength":253,"minLength":1},"description":"Domains reachable when access = ALLOWLIST."}}},"revision":{"type":"number","description":"Snapshot revision this update is based on (optimistic locking). When it does not match the current revision, RevisionConflictException is returned."}},"required":["name","revision"]}}},"delete":{"operationId":"DeleteSnapshot","summary":"","description":"Deletes a snapshot. Fails with ConflictException while any agent references it, unless force=true.\n\n### Allowed Auth Scopes\n\n- wilma-sandbox:DeleteSnapshot","parameters":[{"name":"snapshotId","in":"path","required":true,"schema":{"type":"string","maxLength":12,"minLength":12,"description":"Stable identifier of a snapshot. Agents reference this id in their sandbox capability."},"description":"Stable identifier of a snapshot. Agents reference this id in their sandbox capability."},{"name":"company","in":"query","required":false,"schema":{"type":"string","maxLength":30,"minLength":3}},{"name":"force","in":"query","required":false,"schema":{"type":"boolean","description":"Delete even if agents still reference the snapshot. Those references will fail to launch sandboxes until repointed."},"description":"Delete even if agents still reference the snapshot. Those references will fail to launch sandboxes until repointed."}]}},"/v2/sandbox/snapshots/{snapshotId}/build-logs":{"get":{"operationId":"GetSnapshotBuildLogs","summary":"","description":"Returns the build logs of a snapshot, oldest first. Poll while the snapshot is BUILDING to follow progress, or read after a FAILED build to diagnose it.\n\n### Allowed Auth Scopes\n\n- wilma-sandbox:GetSnapshotBuildLogs\n- wilma-sandbox:read","parameters":[{"name":"snapshotId","in":"path","required":true,"schema":{"type":"string","maxLength":12,"minLength":12,"description":"Stable identifier of a snapshot. Agents reference this id in their sandbox capability."},"description":"Stable identifier of a snapshot. Agents reference this id in their sandbox capability."},{"name":"company","in":"query","required":false,"schema":{"type":"string","maxLength":30,"minLength":3}},{"name":"cursor","in":"query","required":false,"schema":{"type":"string","maxLength":2048,"minLength":1,"description":"Return log lines recorded at or after this time; pass back `nextCursor` to page."},"description":"Return log lines recorded at or after this time; pass back `nextCursor` to page."},{"name":"limit","in":"query","required":false,"schema":{"type":"number","maximum":1000,"minimum":1}}]}},"/v2/sandbox/snapshots/{snapshotId}/rebuild":{"post":{"operationId":"RebuildSnapshot","summary":"","description":"Queues a new image build from the snapshot's current configuration, without changing it. Use it to pick up moving build inputs or to retry a FAILED build. Returns immediately with the snapshot in PENDING/BUILDING status; poll GetSnapshot (or watch GetSnapshotBuildLogs) until it reaches READY or FAILED. Rejected with ConflictException while a build is already pending or running. The last successful image remains available until the new build succeeds; existing session workspaces remain pinned to their original image.\n\n### Allowed Auth Scopes\n\n- wilma-sandbox:RebuildSnapshot","parameters":[{"name":"snapshotId","in":"path","required":true,"schema":{"type":"string","maxLength":12,"minLength":12,"description":"Stable identifier of a snapshot. Agents reference this id in their sandbox capability."},"description":"Stable identifier of a snapshot. Agents reference this id in their sandbox capability."},{"name":"company","in":"query","required":false,"schema":{"type":"string","maxLength":30,"minLength":3}}]}},"/v2/sandbox/snapshots/{snapshotId}/test":{"post":{"operationId":"TestSnapshot","summary":"","description":"Launches a short-lived sandbox from a READY snapshot, runs a command in it and returns the result. Use it to verify a snapshot behaves as expected before pointing agents at it.\n\n### Allowed Auth Scopes\n\n- wilma-sandbox:TestSnapshot","parameters":[{"name":"snapshotId","in":"path","required":true,"schema":{"type":"string","maxLength":12,"minLength":12,"description":"Stable identifier of a snapshot. Agents reference this id in their sandbox capability."},"description":"Stable identifier of a snapshot. Agents reference this id in their sandbox capability."},{"name":"company","in":"query","required":false,"schema":{"type":"string","maxLength":30,"minLength":3}}],"requestBody":{"required":false,"schema":{"type":"object","properties":{"command":{"type":"string","maxLength":4000,"minLength":1,"description":"Command to run in the sandbox. Defaults to a no-op health check that only verifies the sandbox boots."},"timeoutSeconds":{"type":"number","maximum":300,"minimum":1,"description":"Maximum run time in seconds. Defaults to 60."}}}}}},"/v2/secrets":{"get":{"operationId":"ListSecrets","summary":"","description":"Lists company secrets (metadata only).\n\n### Allowed Auth Scopes\n\n- wilma-secrets:ListSecrets\n- wilma-secrets:read","parameters":[{"name":"company","in":"query","required":false,"schema":{"type":"string","maxLength":30,"minLength":3,"description":"The unique identifier of the tenant when a service token is used."},"description":"The unique identifier of the tenant when a service token is used."},{"name":"variant","in":"query","required":false,"schema":{"type":"string","maxLength":2048,"minLength":1,"description":"Filter by the `variant` attribute, e.g. 'auth_client_credentials'. Omit to list every secret."},"description":"Filter by the `variant` attribute, e.g. 'auth_client_credentials'. Omit to list every secret."},{"name":"status","in":"query","required":false,"schema":{"type":"string","description":"Lifecycle state of a secret.","enum":["pending","active"]},"description":"Filter by lifecycle state. Omit to list every secret."},{"name":"search","in":"query","required":false,"schema":{"type":"string","description":"Case-insensitive filter on the secret's name and description. Combined with the other\nfilters by AND. Without it a console can only search the page it has already loaded, which\nin a company past one page answers \"no such credential\" for one that exists."},"description":"Case-insensitive filter on the secret's name and description. Combined with the other\nfilters by AND. Without it a console can only search the page it has already loaded, which\nin a company past one page answers \"no such credential\" for one that exists."},{"name":"limit","in":"query","required":false,"schema":{"type":"number","maximum":100,"minimum":1}},{"name":"cursor","in":"query","required":false,"schema":{"type":"string","maxLength":2048,"minLength":1,"description":"Opaque cursor returned by a previous page; pass it back to fetch the next page."},"description":"Opaque cursor returned by a previous page; pass it back to fetch the next page."}]},"post":{"operationId":"CreateSecret","summary":"","description":"Creates a company secret. The value is stored envelope-encrypted and never returned.\n`value` is optional: a secret created without one is referenceable immediately but stays\n`PENDING` until `RotateSecret` supplies a value.\n\n### Allowed Auth Scopes\n\n- wilma-secrets:CreateSecret","parameters":[{"name":"company","in":"query","required":false,"schema":{"type":"string","maxLength":30,"minLength":3,"description":"The unique identifier of the tenant when a service token is used."},"description":"The unique identifier of the tenant when a service token is used."}],"requestBody":{"required":true,"schema":{"type":"object","properties":{"name":{"type":"string","maxLength":128,"minLength":1,"pattern":"^[a-zA-Z0-9_.-]+$","description":"Human- and machine-readable secret name, unique per company."},"description":{"type":"string","maxLength":1024,"minLength":1},"value":{"type":"string","maxLength":65536,"minLength":1,"description":"Secret material. Write-only; stored envelope-encrypted. Omit to create the secret\nwithout one: it stays `PENDING` until `RotateSecret` supplies a value.","format":"password"},"attributes":{"type":"object","additionalProperties":{"type":"string","maxLength":2048,"minLength":1},"propertyNames":{"type":"string","maxLength":128,"minLength":1},"description":"Non-secret companion fields, returned on management responses (the sensitive half stays in\n`value`). An optional `variant` key classifies the secret's shape (e.g. 'auth_basic',\n'auth_bearer', 'auth_client_credentials'); the remaining keys are the companions that shape\nimplies (e.g. 'auth_client_credentials' → { clientId, endpointUrl, scope }). Variants are an\nopen convention, not a fixed enum; ListSecrets can filter by `variant`."}},"required":["name"]}}}},"/v2/secrets/connections":{"get":{"operationId":"ListConnections","summary":"","description":"Lists the company's connections, SHARED and USER. Filter by `type`, `provider`, or `user`\n(combined with AND).\n\n### Allowed Auth Scopes\n\n- wilma-secrets:ListConnections\n- wilma-secrets:read","parameters":[{"name":"company","in":"query","required":false,"schema":{"type":"string","maxLength":30,"minLength":3,"description":"The unique identifier of the tenant when a service token is used."},"description":"The unique identifier of the tenant when a service token is used."},{"name":"provider","in":"query","required":false,"schema":{"type":"string","maxLength":128,"minLength":1,"pattern":"^[a-z][a-z0-9._-]*$","description":"Filter by provider key, e.g. 'salesforce'."},"description":"Filter by provider key, e.g. 'salesforce'."},{"name":"type","in":"query","required":false,"schema":{"type":"string","description":"Who a connection's credential belongs to.","enum":["shared","user"]},"description":"Filter by connection type — SHARED (company) or USER (per-end-user)."},{"name":"user","in":"query","required":false,"schema":{"type":"string","maxLength":12,"minLength":12,"description":"Filter by owning end user; returns that user's USER connections. Ignored for SHARED\nconnections, which have no owning user."},"description":"Filter by owning end user; returns that user's USER connections. Ignored for SHARED\nconnections, which have no owning user."},{"name":"serverUrl","in":"query","required":false,"schema":{"type":"string","maxLength":2048,"minLength":1,"description":"Only connections to this remote MCP server. The service derives the server's provider key,\nso a client never has to; combined with `provider`, a key that disagrees matches nothing."},"description":"Only connections to this remote MCP server. The service derives the server's provider key,\nso a client never has to; combined with `provider`, a key that disagrees matches nothing."},{"name":"search","in":"query","required":false,"schema":{"type":"string","description":"Case-insensitive filter on the connection's name and description, and on the owning user's\nname and email for a USER connection. Combined with the other filters by AND."},"description":"Case-insensitive filter on the connection's name and description, and on the owning user's\nname and email for a USER connection. Combined with the other filters by AND."},{"name":"limit","in":"query","required":false,"schema":{"type":"number","maximum":100,"minimum":1}},{"name":"cursor","in":"query","required":false,"schema":{"type":"string","maxLength":2048,"minLength":1,"description":"Opaque cursor returned by a previous page; pass it back to fetch the next page."},"description":"Opaque cursor returned by a previous page; pass it back to fetch the next page."}]},"post":{"operationId":"CreateConnection","summary":"","description":"Creates a company-wide SHARED connection. It starts PENDING; authorize it with\nCreateConnectSession, unless the provider resolves it non-interactively (client-credentials,\nAPI key). USER connections are not created here — each user authorizes the provider themselves.\n\n### Allowed Auth Scopes\n\n- wilma-secrets:CreateConnection","parameters":[{"name":"company","in":"query","required":false,"schema":{"type":"string","maxLength":30,"minLength":3,"description":"The unique identifier of the tenant when a service token is used."},"description":"The unique identifier of the tenant when a service token is used."}],"requestBody":{"required":true,"schema":{"type":"object","properties":{"name":{"type":"string","maxLength":128,"minLength":1,"pattern":"^[a-zA-Z0-9_.-]+$","description":"Connection name, unique per company."},"description":{"type":"string","maxLength":1024,"minLength":1},"provider":{"type":"string","maxLength":128,"minLength":1,"pattern":"^[a-z][a-z0-9._-]*$","description":"Provider key the connection authenticates against, e.g. \"salesforce\".\n\nA remote MCP server is created as `mcp`, with its URL in `connectionConfig.mcp_server_url`. The\nconnection is filed under `mcp_` plus a digest of the server's canonical URL, which is the key\nevery later operation uses: one key for every spelling of one server, and a key that keeps a URL\ncarrying an access key out of request paths and audit rows."},"scopes":{"type":"array","items":{"type":"string"},"description":"OAuth scopes requested for the connection."},"connectionConfig":{"type":"object","additionalProperties":{"type":"string","maxLength":2048,"minLength":1},"propertyNames":{"type":"string","maxLength":64,"minLength":1,"pattern":"^[a-z][a-z0-9_]*$","description":"Key of a provider connection-config field, as the provider's own integration entry names it,\ne.g. `hostname`, `subdomain`, `instance_url`."},"maxProperties":10,"description":"Provider parameters the authorization flow itself needs, e.g. `{\"hostname\": \"eu.app.clio.com\"}`.\n\nA provider may template its OAuth endpoints on these — Clio's authorization and token URLs are\nboth `https://${connectionConfig.hostname}/…` — so for those providers a connection simply\ncannot be authorized without one. Which fields a provider declares, and their allowed values,\nare published on the connector definition as `authorization.connectionConfiguration`.\n\nConfiguration, never credentials. The keys a provider's broker reserves for swapping the OAuth\napplication itself are refused rather than forwarded."}},"required":["name","provider"]}}}},"/v2/secrets/connections/{connectionId}":{"get":{"operationId":"GetConnection","summary":"","description":"Gets a connection by id — SHARED or USER (registry/ownership metadata; never the credential).\n\n### Allowed Auth Scopes\n\n- wilma-secrets:GetConnection\n- wilma-secrets:read","parameters":[{"name":"connectionId","in":"path","required":true,"schema":{"type":"string","maxLength":8,"minLength":8,"pattern":"^[0-9A-Za-z]{8}$","description":"Unique identifier of a configured connection."},"description":"Unique identifier of a configured connection."},{"name":"company","in":"query","required":false,"schema":{"type":"string","maxLength":30,"minLength":3,"description":"The unique identifier of the tenant when a service token is used."},"description":"The unique identifier of the tenant when a service token is used."}]},"put":{"operationId":"UpdateConnection","summary":"","description":"Updates connection metadata (name, description, scopes).\n\n### Allowed Auth Scopes\n\n- wilma-secrets:UpdateConnection","parameters":[{"name":"connectionId","in":"path","required":true,"schema":{"type":"string","maxLength":8,"minLength":8,"pattern":"^[0-9A-Za-z]{8}$","description":"Unique identifier of a configured connection."},"description":"Unique identifier of a configured connection."},{"name":"company","in":"query","required":false,"schema":{"type":"string","maxLength":30,"minLength":3,"description":"The unique identifier of the tenant when a service token is used."},"description":"The unique identifier of the tenant when a service token is used."}],"requestBody":{"required":true,"schema":{"type":"object","properties":{"name":{"type":"string","maxLength":128,"minLength":1,"pattern":"^[a-zA-Z0-9_.-]+$","description":"Connection name, unique per company."},"description":{"type":"string","maxLength":1024,"minLength":1},"scopes":{"type":"array","items":{"type":"string"},"description":"OAuth scopes requested for the connection."}},"required":["name"]}}},"delete":{"operationId":"DeleteConnection","summary":"","description":"Deletes a connection by id — SHARED or USER — and revokes its stored credential. Deleting a\nUSER connection disconnects that user from the provider. Usage is not checked: any dangling\nreference fails to authorize on its next access.\n\n### Allowed Auth Scopes\n\n- wilma-secrets:DeleteConnection","parameters":[{"name":"connectionId","in":"path","required":true,"schema":{"type":"string","maxLength":8,"minLength":8,"pattern":"^[0-9A-Za-z]{8}$","description":"Unique identifier of a configured connection."},"description":"Unique identifier of a configured connection."},{"name":"company","in":"query","required":false,"schema":{"type":"string","maxLength":30,"minLength":3,"description":"The unique identifier of the tenant when a service token is used."},"description":"The unique identifier of the tenant when a service token is used."}]}},"/v2/secrets/connections/{connectionId}/confirm":{"post":{"operationId":"ConfirmConnection","summary":"","description":"Records the connection a finished company authorization produced, and marks the row ACTIVE.\n\nThe SHARED counterpart of ConfirmUserConnection, and the only way a widened scope list can ever\nbe consented to. `UpdateConnection` returns a row to PENDING when its scopes widen, but nothing\ncould move it back: discovery gives up as soon as `externalId` is set, and a successful resolve\npromotes only a row marked ERROR. Discovery cannot stand in for consent either — the credential\nauthorized under the narrower scopes is still in the vault under the same end-user id, so\nre-discovering it would mark the row ACTIVE whether or not anybody re-authorized.\n\nThe browser reports the id the authorization flow returned, and the service verifies it against\nthe provider and against the end user the session was minted for before mapping it. That check\nis what makes this consent rather than a claim.\n\n### Allowed Auth Scopes\n\n- wilma-secrets:ConfirmConnection","parameters":[{"name":"connectionId","in":"path","required":true,"schema":{"type":"string","maxLength":8,"minLength":8,"pattern":"^[0-9A-Za-z]{8}$","description":"Unique identifier of a configured connection."},"description":"Unique identifier of a configured connection."},{"name":"company","in":"query","required":false,"schema":{"type":"string","maxLength":30,"minLength":3,"description":"The unique identifier of the tenant when a service token is used."},"description":"The unique identifier of the tenant when a service token is used."}],"requestBody":{"required":true,"schema":{"type":"object","properties":{"externalConnectionId":{"type":"string","maxLength":256,"minLength":1,"description":"Connection id returned by the authorization flow. Verified against the provider and the\nend user the connect session named before it is stored."}},"required":["externalConnectionId"]}}}},"/v2/secrets/connections/{connectionId}/sessions":{"post":{"operationId":"CreateConnectSession","summary":"","description":"Starts an interactive authorization session for a SHARED connection, returning a session token\nthe frontend uses to complete OAuth or collect an API key / basic credential. Per-user\nauthorization uses CreateUserConnectSession.\n\n### Allowed Auth Scopes\n\n- wilma-secrets:CreateConnectSession","parameters":[{"name":"connectionId","in":"path","required":true,"schema":{"type":"string","maxLength":8,"minLength":8,"pattern":"^[0-9A-Za-z]{8}$","description":"Unique identifier of a configured connection."},"description":"Unique identifier of a configured connection."},{"name":"company","in":"query","required":false,"schema":{"type":"string","maxLength":30,"minLength":3,"description":"The unique identifier of the tenant when a service token is used."},"description":"The unique identifier of the tenant when a service token is used."}]}},"/v2/secrets/connections/{connectionId}/verify":{"post":{"operationId":"VerifyConnection","summary":"","description":"Checks whether a connection's credential actually works, without returning it.\n\nResolves a token server-side and reports only the outcome, so the write-only rule holds and an\nadministrator may call this — unlike GetConnectionToken, which is restricted to service tokens\nbecause it hands back the credential itself. Until this existed, a connection's status only\nmoved when a real tool execution failed, which meant an agent was the thing that discovered a\nbroken credential.\n\nHas the same side effects on the record as a resolve: a connection whose authorization\ncompleted is adopted and becomes ACTIVE, one Nango no longer holds forgets its pointer and\nreturns to PENDING, and one whose credential is refused is marked ERROR.\n\n### Allowed Auth Scopes\n\n- wilma-secrets:VerifyConnection","parameters":[{"name":"connectionId","in":"path","required":true,"schema":{"type":"string","maxLength":8,"minLength":8,"pattern":"^[0-9A-Za-z]{8}$","description":"Unique identifier of a configured connection."},"description":"Unique identifier of a configured connection."},{"name":"company","in":"query","required":false,"schema":{"type":"string","maxLength":30,"minLength":3,"description":"The unique identifier of the tenant when a service token is used."},"description":"The unique identifier of the tenant when a service token is used."}]}},"/v2/secrets/items/{secretId}":{"get":{"operationId":"GetSecret","summary":"","description":"Gets secret metadata. Never returns the value.\n\n### Allowed Auth Scopes\n\n- wilma-secrets:GetSecret\n- wilma-secrets:read","parameters":[{"name":"secretId","in":"path","required":true,"schema":{"type":"string","maxLength":8,"minLength":8,"pattern":"^[0-9A-Za-z]{8}$","description":"Unique identifier of a secret."},"description":"Unique identifier of a secret."},{"name":"company","in":"query","required":false,"schema":{"type":"string","maxLength":30,"minLength":3,"description":"The unique identifier of the tenant when a service token is used."},"description":"The unique identifier of the tenant when a service token is used."}]},"put":{"operationId":"UpdateSecret","summary":"","description":"Updates secret metadata (name, description). Use RotateSecret to change the value.\n\n### Allowed Auth Scopes\n\n- wilma-secrets:UpdateSecret","parameters":[{"name":"secretId","in":"path","required":true,"schema":{"type":"string","maxLength":8,"minLength":8,"pattern":"^[0-9A-Za-z]{8}$","description":"Unique identifier of a secret."},"description":"Unique identifier of a secret."},{"name":"company","in":"query","required":false,"schema":{"type":"string","maxLength":30,"minLength":3,"description":"The unique identifier of the tenant when a service token is used."},"description":"The unique identifier of the tenant when a service token is used."}],"requestBody":{"required":true,"schema":{"type":"object","properties":{"name":{"type":"string","maxLength":128,"minLength":1,"pattern":"^[a-zA-Z0-9_.-]+$","description":"Human- and machine-readable secret name, unique per company."},"description":{"type":"string","maxLength":1024,"minLength":1},"attributes":{"type":"object","additionalProperties":{"type":"string","maxLength":2048,"minLength":1},"propertyNames":{"type":"string","maxLength":128,"minLength":1},"description":"Non-secret companion fields, returned on management responses (the sensitive half stays in\n`value`). An optional `variant` key classifies the secret's shape (e.g. 'auth_basic',\n'auth_bearer', 'auth_client_credentials'); the remaining keys are the companions that shape\nimplies (e.g. 'auth_client_credentials' → { clientId, endpointUrl, scope }). Variants are an\nopen convention, not a fixed enum; ListSecrets can filter by `variant`."}},"required":["name"]}}},"delete":{"operationId":"DeleteSecret","summary":"","description":"Deletes a secret. Usage is not verified: any reference that still points at it fails to\nresolve on its next access.\n\n### Allowed Auth Scopes\n\n- wilma-secrets:DeleteSecret","parameters":[{"name":"secretId","in":"path","required":true,"schema":{"type":"string","maxLength":8,"minLength":8,"pattern":"^[0-9A-Za-z]{8}$","description":"Unique identifier of a secret."},"description":"Unique identifier of a secret."},{"name":"company","in":"query","required":false,"schema":{"type":"string","maxLength":30,"minLength":3,"description":"The unique identifier of the tenant when a service token is used."},"description":"The unique identifier of the tenant when a service token is used."}]}},"/v2/secrets/items/{secretId}/rotate":{"post":{"operationId":"RotateSecret","summary":"","description":"Rotates the secret to a new value in place and bumps its revision. References always\nresolve to the current value; consumers pick up the new value on their next access.\n\n### Allowed Auth Scopes\n\n- wilma-secrets:RotateSecret","parameters":[{"name":"secretId","in":"path","required":true,"schema":{"type":"string","maxLength":8,"minLength":8,"pattern":"^[0-9A-Za-z]{8}$","description":"Unique identifier of a secret."},"description":"Unique identifier of a secret."},{"name":"company","in":"query","required":false,"schema":{"type":"string","maxLength":30,"minLength":3,"description":"The unique identifier of the tenant when a service token is used."},"description":"The unique identifier of the tenant when a service token is used."}],"requestBody":{"required":true,"schema":{"type":"object","properties":{"value":{"type":"string","maxLength":65536,"minLength":1,"description":"Plaintext secret material. Accepted as input and returned only by GetSecretValue, never on\nmanagement responses. Stored envelope-encrypted. For a structured secret (e.g. variant\n'auth_client_credentials') this is the secret half; non-secret companions live in `attributes`.","format":"password"}},"required":["value"]}}}},"/v2/secrets/user-connections/{provider}/confirm":{"post":{"operationId":"ConfirmUserConnection","summary":"","description":"Completes per-user authorization by recording the connection id returned by the authorization\nflow. The service validates it against the provider and user, maps (provider, user) →\nconnection, and marks the USER connection ACTIVE.\n\n### Allowed Auth Scopes\n\n- wilma-secrets:ConfirmUserConnection","parameters":[{"name":"provider","in":"path","required":true,"schema":{"type":"string","maxLength":128,"minLength":1,"pattern":"^[a-z][a-z0-9._-]*$","description":"Provider key the connection authenticates against, e.g. \"salesforce\".\n\nA remote MCP server is created as `mcp`, with its URL in `connectionConfig.mcp_server_url`. The\nconnection is filed under `mcp_` plus a digest of the server's canonical URL, which is the key\nevery later operation uses: one key for every spelling of one server, and a key that keeps a URL\ncarrying an access key out of request paths and audit rows."},"description":"Provider key the connection authenticates against, e.g. \"salesforce\".\n\nA remote MCP server is created as `mcp`, with its URL in `connectionConfig.mcp_server_url`. The\nconnection is filed under `mcp_` plus a digest of the server's canonical URL, which is the key\nevery later operation uses: one key for every spelling of one server, and a key that keeps a URL\ncarrying an access key out of request paths and audit rows."},{"name":"company","in":"query","required":false,"schema":{"type":"string","maxLength":30,"minLength":3,"description":"The unique identifier of the tenant when a service token is used."},"description":"The unique identifier of the tenant when a service token is used."},{"name":"user","in":"query","required":false,"schema":{"type":"string","maxLength":12,"minLength":12,"description":"Subject user. Defaults to the caller for an xbs token; required for a service token."},"description":"Subject user. Defaults to the caller for an xbs token; required for a service token."}],"requestBody":{"required":true,"schema":{"type":"object","properties":{"connectionId":{"type":"string","maxLength":256,"minLength":1,"description":"Connection id returned by the authorization flow."}},"required":["connectionId"]}}}},"/v2/secrets/user-connections/{provider}/sessions":{"post":{"operationId":"CreateUserConnectSession","summary":"","description":"Starts an interactive authorization session for the calling user against a provider, returning\na session token the frontend uses to run the authorization flow. On success the frontend\nreports the resulting connection id back via ConfirmUserConnection.\n\n### Allowed Auth Scopes\n\n- wilma-secrets:CreateUserConnectSession","parameters":[{"name":"provider","in":"path","required":true,"schema":{"type":"string","maxLength":128,"minLength":1,"pattern":"^[a-z][a-z0-9._-]*$","description":"Provider key the connection authenticates against, e.g. \"salesforce\".\n\nA remote MCP server is created as `mcp`, with its URL in `connectionConfig.mcp_server_url`. The\nconnection is filed under `mcp_` plus a digest of the server's canonical URL, which is the key\nevery later operation uses: one key for every spelling of one server, and a key that keeps a URL\ncarrying an access key out of request paths and audit rows."},"description":"Provider key the connection authenticates against, e.g. \"salesforce\".\n\nA remote MCP server is created as `mcp`, with its URL in `connectionConfig.mcp_server_url`. The\nconnection is filed under `mcp_` plus a digest of the server's canonical URL, which is the key\nevery later operation uses: one key for every spelling of one server, and a key that keeps a URL\ncarrying an access key out of request paths and audit rows."},{"name":"company","in":"query","required":false,"schema":{"type":"string","maxLength":30,"minLength":3,"description":"The unique identifier of the tenant when a service token is used."},"description":"The unique identifier of the tenant when a service token is used."},{"name":"user","in":"query","required":false,"schema":{"type":"string","maxLength":12,"minLength":12,"description":"Subject user. Defaults to the caller for an xbs token; required for a service token."},"description":"Subject user. Defaults to the caller for an xbs token; required for a service token."}],"requestBody":{"required":false,"schema":{"type":"object","properties":{"connectionConfig":{"type":"object","additionalProperties":{"type":"string","maxLength":2048,"minLength":1},"propertyNames":{"type":"string","maxLength":64,"minLength":1,"pattern":"^[a-z][a-z0-9_]*$","description":"Key of a provider connection-config field, as the provider's own integration entry names it,\ne.g. `hostname`, `subdomain`, `instance_url`."},"maxProperties":10,"description":"Provider parameters the authorization flow itself needs, e.g. `{\"hostname\": \"eu.app.clio.com\"}`.\n\nA provider may template its OAuth endpoints on these — Clio's authorization and token URLs are\nboth `https://${connectionConfig.hostname}/…` — so for those providers a connection simply\ncannot be authorized without one. Which fields a provider declares, and their allowed values,\nare published on the connector definition as `authorization.connectionConfiguration`.\n\nConfiguration, never credentials. The keys a provider's broker reserves for swapping the OAuth\napplication itself are refused rather than forwarded."}}}}}},"/v2/skills":{"get":{"operationId":"ListSkills","summary":"","description":"Lists skills visible to the calling user.\n\n### Allowed Auth Scopes\n\n- wilma-skills:ListSkills\n- wilma-skills:read","parameters":[{"name":"company","in":"query","required":false,"schema":{"type":"string","maxLength":30,"minLength":3}},{"name":"user","in":"query","required":false,"schema":{"type":"string","maxLength":12,"minLength":12}},{"name":"scope","in":"query","required":false,"schema":{"type":"string","enum":["mine","company","all","everyone"]},"description":"Filter by visibility to the caller. Defaults to `all`."},{"name":"status","in":"query","required":false,"schema":{"type":"string","enum":["draft","active"]}},{"name":"search","in":"query","required":false,"schema":{"type":"string","description":"Filter by name or description."},"description":"Filter by name or description."},{"name":"limit","in":"query","required":false,"schema":{"type":"number","maximum":100,"minimum":1}},{"name":"cursor","in":"query","required":false,"schema":{"type":"string","maxLength":2048,"minLength":1,"description":"Opaque cursor returned by a previous page; pass it back to fetch the next page."},"description":"Opaque cursor returned by a previous page; pass it back to fetch the next page."}]},"post":{"operationId":"CreateSkill","summary":"","description":"Creates a new skill draft owned by the caller, complete with its bundled files: upload them with UploadSkillFile first and pass the returned ids in `files`. Defaults to private visibility. The skill serves no traffic until its first version is published.\n\n### Allowed Auth Scopes\n\n- wilma-skills:CreateSkill","parameters":[{"name":"company","in":"query","required":false,"schema":{"type":"string","maxLength":30,"minLength":3}},{"name":"user","in":"query","required":false,"schema":{"type":"string","maxLength":12,"minLength":12}}],"requestBody":{"required":true,"schema":{"type":"object","properties":{"description":{"type":"string","maxLength":1024,"description":"What the skill does and when to use it. Loaded at discovery time for progressive disclosure."},"content":{"type":"string","maxLength":204800,"minLength":1,"description":"The SKILL.md markdown body: the instructions themselves. Progressive disclosure: loaded when the\n skill is activated. Keep it concise and move detail into bundled files the agent reads on demand."},"tools":{"type":"array","items":{"description":"A tool a skill depends on. Uses the same references an agent uses in `engine.ai.capabilities`, so a tool is configured identically whether it hangs off an agent or off a skill.","oneOf":[{"type":"object","title":"tool","properties":{"tool":{"type":"object","properties":{"id":{"type":"string","maxLength":12,"minLength":12,"pattern":"^[0-9A-Za-z]{12}$","description":"Unique identifier of the tool using nanoid format"},"toolVersion":{"type":"number","minimum":1,"description":"Pin a specific published tool version. When omitted, the tool's latestVersion is resolved at load, so a republished tool reaches this agent without a new agent version. Pin it when `fields` must keep aiming at an exact input schema."},"description":{"type":"string","maxLength":4096,"minLength":1,"description":"Override the tool's description for this agent. When omitted, the tool's own description is used."},"fields":{"type":"array","items":{"type":"object","description":"Per-field override applied to a shared tool's input schema for this agent.","properties":{"path":{"type":"string","maxLength":512,"minLength":1,"pattern":"^/.*$","description":"JSON Pointer to the target property in the tool input schema."},"handler":{"oneOf":[{"type":"object","title":"guided","properties":{"guided":{"type":"object","description":"Keeps the field model-fillable but overrides its description in the schema shown to the model.","properties":{"description":{"type":"string","maxLength":1024,"minLength":1}},"required":["description"]}},"required":["guided"]},{"type":"object","title":"predefined","properties":{"predefined":{"type":"object","description":"A fixed value injected at the field path and excluded from the schema shown to the model, so the model neither sees nor sets it. A JSON value, or a string with handlebars templates resolved against agent variables and session context (e.g. '{{callerName}}').","properties":{"value":{}},"required":["value"]}},"required":["predefined"]}]}},"required":["handler","path"]},"description":"Per-field overrides on the tool input schema: pin fixed values (hidden from the model) or refine field descriptions. Fields not listed are filled by the model per the tool schema. Paths are resolved against the input schema of the tool version in effect — pin `toolVersion` when they must not move."},"startMessage":{"type":"string","description":"Message spoken when the tool call starts. Honoured on the voice channel. Spoken in every language of the call while `startMessages` is absent or empty; not consulted once `startMessages` carries any entry."},"startMessages":{"type":"array","items":{"type":"object","description":"One phrase in one language, for a capability that speaks a fixed line on the voice channel.\n Which entry is spoken is decided by the call's current language — the one the call resolved\n to at connect, or the one automatic detection switched to — matched on `language`\n CASE-INSENSITIVELY, the same rule `VoiceBotSpokenPhrase` states for the legacy stack, so a\n code written in either case reaches the same entry on either. Blank `text` means the agent\n says nothing in that language; it does NOT fall back to the language-agnostic member beside\n the list, which would speak one language's text in another language's voice.","properties":{"language":{"type":"string","maxLength":8,"minLength":2,"pattern":"^[a-z]{2,3}(-[A-Z]{2})?$","description":"BCP-47 language tag, optionally with a region subtag: 'en', 'en-US', 'pt-BR'. Deliberately a\n constrained string rather than an enum: the offerable set is a property of the selected\n recogniser and changes with it, so an enumeration here would cap the set at whatever was known\n when the model was released."},"text":{"type":"string"}},"required":["language","text"]},"description":"Per-language start messages. When present and non-empty, the entry for the call's current\n language is spoken and `startMessage` is not consulted; a language with no entry is silent.\n When absent or empty, `startMessage` is spoken as before. On a call with no effective speech\n configuration — no configured languages, or a PBX below protocol 2 — `startMessage` is spoken\n regardless, because there is no current language to choose by."},"pipeline":{"type":"object","properties":{"type":{"type":"string","description":"How the model waits for a tool and whether it synthesizes a reply from the result.","enum":["blocking_request","blocking_request_guided","async_request","async_request_guided"]},"instructions":{"type":"string","description":"Reply instructions for the *_GUIDED pipeline types. Use {{response}} to reference the tool result."}}},"approval":{"type":"object","description":"Human-in-the-loop confirmation for a tool. Presence requires approval: the agent pauses before running the tool and the session user (whoever controls the session) approves or denies inline.","properties":{"prompt":{"type":"string","description":"Optional message shown in the confirmation prompt. Supports handlebars against agent variables and the pending tool arguments. When omitted, a default prompt is shown."}}},"channels":{"type":"array","items":{"type":"string","description":"A channel an agent can serve (see AgentChannels for enablement). Also the runtime channel recorded by the channel runtimes / wilma-traces. A background task has no delivery surface and therefore no channel.","enum":["voice","chat","assistant"]},"uniqueItems":true,"description":"Channels this capability is offered on. Absent or empty — the default — means every channel the agent serves\n that can honour it. Listing channels narrows that set and never widens it: a `transfer` listed for `chat` is\n still ignored there, because the chat channel has no call to transfer."},"annotations":{"type":"array","items":{"description":"Configuration for annotations emitted after a tool completes.","oneOf":[{"type":"object","title":"tag","properties":{"tag":{"type":"object","description":"Tag annotation emitted after a tool completes.","properties":{"value":{"type":"string","description":"Static handlebars expression resolved against agent variables and helpers."},"applyOnSuccess":{"type":"boolean","description":"Apply when the tool succeeds."},"applyOnFailure":{"type":"boolean","description":"Apply when the tool fails."}}}},"required":["tag"]},{"type":"object","title":"comment","properties":{"comment":{"type":"object","description":"Comment annotation emitted after a tool completes.","properties":{"value":{"type":"string","description":"Static handlebars expression resolved against agent variables and helpers."},"applyOnSuccess":{"type":"boolean","description":"Apply when the tool succeeds."},"applyOnFailure":{"type":"boolean","description":"Apply when the tool fails."}}}},"required":["comment"]},{"type":"object","title":"variables","properties":{"variables":{"type":"object","description":"Variables annotation emitted after a tool completes."}},"required":["variables"]},{"type":"object","title":"result","properties":{"result":{"type":"object","description":"Result annotation emitted after a tool completes."}},"required":["result"]}]}}},"required":["id"]}},"required":["tool"]},{"type":"object","title":"connector","properties":{"connector":{"type":"object","description":"A configured connector (wilma-tools) exposed to the model as a single meta-tool named\n after the connector. The model calls the reserved `help` action to learn the callable\n actions, then dispatches an action through the same tool. Progressive disclosure runs\n through the connector tool itself, not the skills catalogue.","properties":{"id":{"type":"string","maxLength":12,"minLength":12,"pattern":"^[0-9A-Za-z]{12}$","description":"Configured connector instance in wilma-tools. Its pinned definition, permission `grant` and authorization govern what the model can discover via `help` and call. There is no version pin here, unlike `AgentTool.toolVersion`: the action catalogue and its schemas are already frozen by the connector's pinned definition, while `grant` and authorization are read live so permission changes apply at once."},"startMessage":{"type":"string","description":"Message spoken when a connector action call starts. Honoured on the voice channel. Mirrors `AgentTool.startMessage`. Not applied to the read-only `help` action. Not consulted once `startMessages` carries any entry."},"startMessages":{"type":"array","items":{"type":"object","description":"One phrase in one language, for a capability that speaks a fixed line on the voice channel.\n Which entry is spoken is decided by the call's current language — the one the call resolved\n to at connect, or the one automatic detection switched to — matched on `language`\n CASE-INSENSITIVELY, the same rule `VoiceBotSpokenPhrase` states for the legacy stack, so a\n code written in either case reaches the same entry on either. Blank `text` means the agent\n says nothing in that language; it does NOT fall back to the language-agnostic member beside\n the list, which would speak one language's text in another language's voice.","properties":{"language":{"type":"string","maxLength":8,"minLength":2,"pattern":"^[a-z]{2,3}(-[A-Z]{2})?$","description":"BCP-47 language tag, optionally with a region subtag: 'en', 'en-US', 'pt-BR'. Deliberately a\n constrained string rather than an enum: the offerable set is a property of the selected\n recogniser and changes with it, so an enumeration here would cap the set at whatever was known\n when the model was released."},"text":{"type":"string"}},"required":["language","text"]},"description":"Forward-compatible per-language connector start messages, mirroring `AgentTool.startMessages`. Accepted and stored so clients can configure connector and tool phrases consistently, but currently ignored at execution time because the voice runtime does not read connector start messages yet. Runtime enablement is tracked in wilma-services docs/todo.md item 8."},"pipeline":{"type":"object","properties":{"type":{"type":"string","description":"How the model waits for a tool and whether it synthesizes a reply from the result.","enum":["blocking_request","blocking_request_guided","async_request","async_request_guided"]},"instructions":{"type":"string","description":"Reply instructions for the *_GUIDED pipeline types. Use {{response}} to reference the tool result."}}},"approval":{"type":"object","description":"Human-in-the-loop confirmation for a tool. Presence requires approval: the agent pauses before running the tool and the session user (whoever controls the session) approves or denies inline.","properties":{"prompt":{"type":"string","description":"Optional message shown in the confirmation prompt. Supports handlebars against agent variables and the pending tool arguments. When omitted, a default prompt is shown."}}},"channels":{"type":"array","items":{"type":"string","description":"A channel an agent can serve (see AgentChannels for enablement). Also the runtime channel recorded by the channel runtimes / wilma-traces. A background task has no delivery surface and therefore no channel.","enum":["voice","chat","assistant"]},"uniqueItems":true,"description":"Channels this capability is offered on. Absent or empty — the default — means every channel the agent serves\n that can honour it. Listing channels narrows that set and never widens it: a `transfer` listed for `chat` is\n still ignored there, because the chat channel has no call to transfer."},"annotations":{"type":"array","items":{"description":"Configuration for annotations emitted after a tool completes.","oneOf":[{"type":"object","title":"tag","properties":{"tag":{"type":"object","description":"Tag annotation emitted after a tool completes.","properties":{"value":{"type":"string","description":"Static handlebars expression resolved against agent variables and helpers."},"applyOnSuccess":{"type":"boolean","description":"Apply when the tool succeeds."},"applyOnFailure":{"type":"boolean","description":"Apply when the tool fails."}}}},"required":["tag"]},{"type":"object","title":"comment","properties":{"comment":{"type":"object","description":"Comment annotation emitted after a tool completes.","properties":{"value":{"type":"string","description":"Static handlebars expression resolved against agent variables and helpers."},"applyOnSuccess":{"type":"boolean","description":"Apply when the tool succeeds."},"applyOnFailure":{"type":"boolean","description":"Apply when the tool fails."}}}},"required":["comment"]},{"type":"object","title":"variables","properties":{"variables":{"type":"object","description":"Variables annotation emitted after a tool completes."}},"required":["variables"]},{"type":"object","title":"result","properties":{"result":{"type":"object","description":"Result annotation emitted after a tool completes."}},"required":["result"]}]}}},"required":["id"]}},"required":["connector"]},{"type":"object","title":"kb","properties":{"kb":{"type":"object","properties":{"knowledgeBaseId":{"type":"string"},"instructions":{"type":"string","maxLength":60000,"minLength":1,"description":"Additional instructions for when the agent should use this knowledge base."},"searchStrategy":{"type":"string","description":"Search algorithm used to retrieve passages.","enum":["bm25","vector","hybrid"]},"searchThreshold":{"type":"string","description":"Minimum similarity a passage needs to be returned. Raising it trades recall for precision.","enum":["low","medium","high"]},"searchLimit":{"type":"number","maximum":50,"minimum":1,"description":"Maximum number of passages this capability returns. Defaults to the knowledge base service's own default (10) when omitted. Keep it low on voice: every passage is prompt the model reads before it can speak."},"channels":{"type":"array","items":{"type":"string","description":"A channel an agent can serve (see AgentChannels for enablement). Also the runtime channel recorded by the channel runtimes / wilma-traces. A background task has no delivery surface and therefore no channel.","enum":["voice","chat","assistant"]},"uniqueItems":true,"description":"Channels this capability is offered on. Absent or empty — the default — means every channel the agent serves\n that can honour it. Listing channels narrows that set and never widens it: a `transfer` listed for `chat` is\n still ignored there, because the chat channel has no call to transfer."},"annotations":{"type":"array","items":{"description":"Configuration for annotations emitted after a tool completes.","oneOf":[{"type":"object","title":"tag","properties":{"tag":{"type":"object","description":"Tag annotation emitted after a tool completes.","properties":{"value":{"type":"string","description":"Static handlebars expression resolved against agent variables and helpers."},"applyOnSuccess":{"type":"boolean","description":"Apply when the tool succeeds."},"applyOnFailure":{"type":"boolean","description":"Apply when the tool fails."}}}},"required":["tag"]},{"type":"object","title":"comment","properties":{"comment":{"type":"object","description":"Comment annotation emitted after a tool completes.","properties":{"value":{"type":"string","description":"Static handlebars expression resolved against agent variables and helpers."},"applyOnSuccess":{"type":"boolean","description":"Apply when the tool succeeds."},"applyOnFailure":{"type":"boolean","description":"Apply when the tool fails."}}}},"required":["comment"]},{"type":"object","title":"variables","properties":{"variables":{"type":"object","description":"Variables annotation emitted after a tool completes."}},"required":["variables"]},{"type":"object","title":"result","properties":{"result":{"type":"object","description":"Result annotation emitted after a tool completes."}},"required":["result"]}]}}}}},"required":["kb"]},{"type":"object","title":"webSearch","properties":{"webSearch":{"type":"object","description":"Model-native web search. The provider tool is selected from the agent model.","properties":{"contextSize":{"type":"string","description":"Search depth/context hint, mapped per provider.","enum":["low","medium","high"]},"allowedDomains":{"type":"array","items":{"type":"string","maxLength":253,"minLength":1},"description":"Restrict results to these domains where the provider supports it."},"userLocation":{"type":"object","properties":{"country":{"type":"string","description":"ISO 3166-1 alpha-2 country code, e.g. 'US'."},"region":{"type":"string"},"city":{"type":"string"},"timezone":{"type":"string","description":"IANA timezone, e.g. 'Europe/Rome'."}}},"channels":{"type":"array","items":{"type":"string","description":"A channel an agent can serve (see AgentChannels for enablement). Also the runtime channel recorded by the channel runtimes / wilma-traces. A background task has no delivery surface and therefore no channel.","enum":["voice","chat","assistant"]},"uniqueItems":true,"description":"Channels this capability is offered on. Absent or empty — the default — means every channel the agent serves\n that can honour it. Listing channels narrows that set and never widens it: a `transfer` listed for `chat` is\n still ignored there, because the chat channel has no call to transfer."}}}},"required":["webSearch"]},{"type":"object","title":"webFetch","properties":{"webFetch":{"type":"object","description":"Server-side fetch of a URL, returning its readable text/markdown to the model.","properties":{"allowedDomains":{"type":"array","items":{"type":"string","maxLength":253,"minLength":1},"description":"Restrict fetches to these domains. Empty list means any domain is allowed."},"channels":{"type":"array","items":{"type":"string","description":"A channel an agent can serve (see AgentChannels for enablement). Also the runtime channel recorded by the channel runtimes / wilma-traces. A background task has no delivery surface and therefore no channel.","enum":["voice","chat","assistant"]},"uniqueItems":true,"description":"Channels this capability is offered on. Absent or empty — the default — means every channel the agent serves\n that can honour it. Listing channels narrows that set and never widens it: a `transfer` listed for `chat` is\n still ignored there, because the chat channel has no call to transfer."}}}},"required":["webFetch"]},{"type":"object","title":"transfer","properties":{"transfer":{"type":"object","description":"Transfer the call to a PBX destination. Honoured on the voice channel. One capability entry per transfer target.","properties":{"name":{"type":"string","description":"Tool name exposed to the model. Auto-generated when omitted."},"description":{"type":"string","description":"Description for the model to decide when to transfer."},"variant":{"type":"string","enum":["predefined","dynamic","directory"]},"context":{"type":"string","description":"Routing context for PREDEFINED transfers."},"extension":{"type":"string","description":"Extension or phone number for PREDEFINED transfers."},"pipeline":{"type":"object","properties":{"type":{"type":"string","enum":["transfer","reply_and_transfer","reply_instructed_and_transfer"]},"instructions":{"type":"string","description":"Instructions for the model reply when type is REPLY_INSTRUCTED_AND_TRANSFER."}}},"channels":{"type":"array","items":{"type":"string","description":"A channel an agent can serve (see AgentChannels for enablement). Also the runtime channel recorded by the channel runtimes / wilma-traces. A background task has no delivery surface and therefore no channel.","enum":["voice","chat","assistant"]},"uniqueItems":true,"description":"Channels this capability is offered on. Absent or empty — the default — means every channel the agent serves\n that can honour it. Listing channels narrows that set and never widens it: a `transfer` listed for `chat` is\n still ignored there, because the chat channel has no call to transfer."},"annotations":{"type":"array","items":{"description":"Configuration for annotations emitted after a tool completes.","oneOf":[{"type":"object","title":"tag","properties":{"tag":{"type":"object","description":"Tag annotation emitted after a tool completes.","properties":{"value":{"type":"string","description":"Static handlebars expression resolved against agent variables and helpers."},"applyOnSuccess":{"type":"boolean","description":"Apply when the tool succeeds."},"applyOnFailure":{"type":"boolean","description":"Apply when the tool fails."}}}},"required":["tag"]},{"type":"object","title":"comment","properties":{"comment":{"type":"object","description":"Comment annotation emitted after a tool completes.","properties":{"value":{"type":"string","description":"Static handlebars expression resolved against agent variables and helpers."},"applyOnSuccess":{"type":"boolean","description":"Apply when the tool succeeds."},"applyOnFailure":{"type":"boolean","description":"Apply when the tool fails."}}}},"required":["comment"]},{"type":"object","title":"variables","properties":{"variables":{"type":"object","description":"Variables annotation emitted after a tool completes."}},"required":["variables"]},{"type":"object","title":"result","properties":{"result":{"type":"object","description":"Result annotation emitted after a tool completes."}},"required":["result"]}]}}}}},"required":["transfer"]},{"type":"object","title":"hangup","properties":{"hangup":{"type":"object","description":"Let the model end the call. Honoured on the voice channel.","properties":{"name":{"type":"string","description":"Tool name exposed to the model. Auto-generated when omitted."},"description":{"type":"string","description":"Description for the model to decide when to hang up."},"pipeline":{"type":"object","properties":{"type":{"type":"string","enum":["hangup","reply_and_hangup","reply_instructed_and_hangup"]},"instructions":{"type":"string","description":"Instructions for the model reply when type is REPLY_INSTRUCTED_AND_HANGUP."}}},"channels":{"type":"array","items":{"type":"string","description":"A channel an agent can serve (see AgentChannels for enablement). Also the runtime channel recorded by the channel runtimes / wilma-traces. A background task has no delivery surface and therefore no channel.","enum":["voice","chat","assistant"]},"uniqueItems":true,"description":"Channels this capability is offered on. Absent or empty — the default — means every channel the agent serves\n that can honour it. Listing channels narrows that set and never widens it: a `transfer` listed for `chat` is\n still ignored there, because the chat channel has no call to transfer."},"annotations":{"type":"array","items":{"description":"Configuration for annotations emitted after a tool completes.","oneOf":[{"type":"object","title":"tag","properties":{"tag":{"type":"object","description":"Tag annotation emitted after a tool completes.","properties":{"value":{"type":"string","description":"Static handlebars expression resolved against agent variables and helpers."},"applyOnSuccess":{"type":"boolean","description":"Apply when the tool succeeds."},"applyOnFailure":{"type":"boolean","description":"Apply when the tool fails."}}}},"required":["tag"]},{"type":"object","title":"comment","properties":{"comment":{"type":"object","description":"Comment annotation emitted after a tool completes.","properties":{"value":{"type":"string","description":"Static handlebars expression resolved against agent variables and helpers."},"applyOnSuccess":{"type":"boolean","description":"Apply when the tool succeeds."},"applyOnFailure":{"type":"boolean","description":"Apply when the tool fails."}}}},"required":["comment"]},{"type":"object","title":"variables","properties":{"variables":{"type":"object","description":"Variables annotation emitted after a tool completes."}},"required":["variables"]},{"type":"object","title":"result","properties":{"result":{"type":"object","description":"Result annotation emitted after a tool completes."}},"required":["result"]}]}}}}},"required":["hangup"]},{"type":"object","title":"wait","properties":{"wait":{"type":"object","description":"Let the model hold the call while it works (e.g. a lookup), keeping the caller on the line. Honoured on the voice channel.","properties":{"name":{"type":"string","description":"Tool name exposed to the model. Auto-generated when omitted."},"description":{"type":"string","description":"Description for the model to decide when to wait."},"message":{"type":"string","description":"Message spoken when entering the wait state. Spoken in every language of the call while `messages` is absent or empty; not consulted once `messages` carries any entry."},"messages":{"type":"array","items":{"type":"object","description":"One phrase in one language, for a capability that speaks a fixed line on the voice channel.\n Which entry is spoken is decided by the call's current language — the one the call resolved\n to at connect, or the one automatic detection switched to — matched on `language`\n CASE-INSENSITIVELY, the same rule `VoiceBotSpokenPhrase` states for the legacy stack, so a\n code written in either case reaches the same entry on either. Blank `text` means the agent\n says nothing in that language; it does NOT fall back to the language-agnostic member beside\n the list, which would speak one language's text in another language's voice.","properties":{"language":{"type":"string","maxLength":8,"minLength":2,"pattern":"^[a-z]{2,3}(-[A-Z]{2})?$","description":"BCP-47 language tag, optionally with a region subtag: 'en', 'en-US', 'pt-BR'. Deliberately a\n constrained string rather than an enum: the offerable set is a property of the selected\n recogniser and changes with it, so an enumeration here would cap the set at whatever was known\n when the model was released."},"text":{"type":"string"}},"required":["language","text"]},"description":"Per-language wait messages. Same rule as `AgentTool.startMessages`: the entry for the call's current language is spoken, a language with no entry is silent, and with no effective speech configuration `message` is spoken."},"channels":{"type":"array","items":{"type":"string","description":"A channel an agent can serve (see AgentChannels for enablement). Also the runtime channel recorded by the channel runtimes / wilma-traces. A background task has no delivery surface and therefore no channel.","enum":["voice","chat","assistant"]},"uniqueItems":true,"description":"Channels this capability is offered on. Absent or empty — the default — means every channel the agent serves\n that can honour it. Listing channels narrows that set and never widens it: a `transfer` listed for `chat` is\n still ignored there, because the chat channel has no call to transfer."},"annotations":{"type":"array","items":{"description":"Configuration for annotations emitted after a tool completes.","oneOf":[{"type":"object","title":"tag","properties":{"tag":{"type":"object","description":"Tag annotation emitted after a tool completes.","properties":{"value":{"type":"string","description":"Static handlebars expression resolved against agent variables and helpers."},"applyOnSuccess":{"type":"boolean","description":"Apply when the tool succeeds."},"applyOnFailure":{"type":"boolean","description":"Apply when the tool fails."}}}},"required":["tag"]},{"type":"object","title":"comment","properties":{"comment":{"type":"object","description":"Comment annotation emitted after a tool completes.","properties":{"value":{"type":"string","description":"Static handlebars expression resolved against agent variables and helpers."},"applyOnSuccess":{"type":"boolean","description":"Apply when the tool succeeds."},"applyOnFailure":{"type":"boolean","description":"Apply when the tool fails."}}}},"required":["comment"]},{"type":"object","title":"variables","properties":{"variables":{"type":"object","description":"Variables annotation emitted after a tool completes."}},"required":["variables"]},{"type":"object","title":"result","properties":{"result":{"type":"object","description":"Result annotation emitted after a tool completes."}},"required":["result"]}]}}}}},"required":["wait"]}]},"description":"Tools the skill's instructions depend on, merged into the agent's toolset while the skill is active."},"files":{"type":"array","items":{"type":"object","description":"A file bundled with a skill: a reference to immutable uploaded content, plus the path it is materialised at.","properties":{"id":{"type":"string","maxLength":12,"minLength":12,"pattern":"^[A-Za-z0-9_-]{12}$","description":"Uploaded content this entry points at, as returned by UploadSkillFile."},"path":{"type":"string","maxLength":1024,"minLength":1,"pattern":"^[a-zA-Z0-9](?:[a-zA-Z0-9._/-]*[a-zA-Z0-9_])?$","description":"Path the file is materialised at inside the bundle."},"name":{"type":"string","maxLength":255,"minLength":1,"description":"File name shown to the user. Defaults to the name recorded at upload."},"size":{"type":"number","description":"Size in bytes. Ignored on input; the service reports the size of the stored content."},"executable":{"type":"boolean","description":"Marks the file executable when it is materialised in the sandbox. Set per skill, not per upload."}},"required":["id","path"]},"maxItems":200,"description":"Files bundled with the skill: references to uploaded content (UploadSkillFile) placed at bundle\n paths. The list is the complete bundle — a file left out of an update is no longer part of the\n skill, and changing a file means uploading a new one and referencing it at the same path."},"name":{"type":"string","maxLength":255,"minLength":1,"description":"The skill's name as people see it: free text, editable, not unique. The identifier the model\n loads a skill by is not stored — it is a slug derived from this name when an agent's tools are\n built for a session, transliterated and numbered (`booking`, `booking-2`) so it is unique among\n the skills attached to that agent. Renaming a skill changes that slug and nothing else."},"access":{"type":"object","description":"Who can use and edit the skill. Usage for private skills is limited to the creator, editors and viewers; company skills are attachable by anyone in the organization.","properties":{"visibility":{"type":"string","enum":["private","company"]},"editors":{"type":"array","items":{"description":"A user or group granted edit access to the skill draft.","oneOf":[{"type":"object","title":"userId","properties":{"userId":{"type":"string","maxLength":12,"minLength":12}},"required":["userId"]},{"type":"object","title":"groupId","properties":{"groupId":{"type":"string"}},"required":["groupId"]}]},"description":"Users and groups that may edit the draft. The creator always has edit access."},"viewers":{"type":"array","items":{"description":"A user or group granted edit access to the skill draft.","oneOf":[{"type":"object","title":"userId","properties":{"userId":{"type":"string","maxLength":12,"minLength":12}},"required":["userId"]},{"type":"object","title":"groupId","properties":{"groupId":{"type":"string"}},"required":["groupId"]}]},"description":"Users and groups the skill is shared with: they may see and attach it, but not edit. Redundant under company visibility."}}}},"required":["content","description","name"]}}}},"/v2/skills/files":{"post":{"operationId":"UploadSkillFile","summary":"","description":"Registers a skill file and returns a short-lived presigned PUT URL for its content. The client\n uploads the bytes directly to storage, then passes the returned `fileId` in the `files` of\n CreateSkill or UpdateSkill, together with the path the file should occupy in the bundle. Files\n are addressed by id alone, so they can be uploaded before the skill exists; a file never\n referenced by a skill is reclaimed. Content is immutable: to replace a file, upload a new one\n and reference it at the same path.\n\n### Allowed Auth Scopes\n\n- wilma-skills:UploadSkillFile","parameters":[{"name":"company","in":"query","required":false,"schema":{"type":"string","maxLength":30,"minLength":3,"description":"The unique identifier of the tenant when a service token is used."},"description":"The unique identifier of the tenant when a service token is used."},{"name":"user","in":"query","required":false,"schema":{"type":"string","maxLength":12,"minLength":12,"description":"The user the upload belongs to. Optional: identifies the user when a service or tenant token is used."},"description":"The user the upload belongs to. Optional: identifies the user when a service or tenant token is used."}],"requestBody":{"required":true,"schema":{"type":"object","properties":{"name":{"type":"string","maxLength":255,"minLength":1,"description":"File name shown to the user, e.g. 'api-reference.md'. Used as the download file name and as the default `name` of a skill file referencing this id."},"size":{"type":"number","description":"Declared size in bytes. Used for quota checks before the presigned URL is issued."},"contentType":{"type":"string","description":"MIME type the client must send as the Content-Type header on PUT, e.g. 'application/pdf'."}},"required":["name","size"]}}}},"/v2/skills/files/items/{fileId}/download":{"get":{"operationId":"DownloadSkillFile","summary":"","description":"Returns a short-lived presigned URL to download a skill file's content, addressed by file id\n alone. Callers who can view a skill referencing the file, and the user who uploaded it, may\n download it.\n\n### Allowed Auth Scopes\n\n- wilma-skills:DownloadSkillFile\n- wilma-skills:read","parameters":[{"name":"fileId","in":"path","required":true,"schema":{"type":"string","maxLength":12,"minLength":12,"pattern":"^[A-Za-z0-9_-]{12}$","description":"Identifier of uploaded file content, minted by UploadSkillFile. The content behind an id is\n immutable; to change a file, upload a new one and reference it at the same path."},"description":"Identifier of uploaded file content, minted by UploadSkillFile. The content behind an id is\n immutable; to change a file, upload a new one and reference it at the same path."},{"name":"company","in":"query","required":false,"schema":{"type":"string","maxLength":30,"minLength":3,"description":"The unique identifier of the tenant when a service token is used."},"description":"The unique identifier of the tenant when a service token is used."},{"name":"user","in":"query","required":false,"schema":{"type":"string","maxLength":12,"minLength":12,"description":"The user requesting the download. Optional: identifies the user when a service or tenant token is used."},"description":"The user requesting the download. Optional: identifies the user when a service or tenant token is used."}]}},"/v2/skills/items/{skillId}":{"get":{"operationId":"GetSkill","summary":"","description":"Gets a skill draft: metadata, SKILL.md content, tools, bundled file references and version state.\n\n### Allowed Auth Scopes\n\n- wilma-skills:GetSkill\n- wilma-skills:read","parameters":[{"name":"skillId","in":"path","required":true,"schema":{"type":"string","maxLength":12,"minLength":12}},{"name":"company","in":"query","required":false,"schema":{"type":"string","maxLength":30,"minLength":3}},{"name":"user","in":"query","required":false,"schema":{"type":"string","maxLength":12,"minLength":12}}]},"put":{"operationId":"UpdateSkill","summary":"","description":"Updates the draft definition or access policy. Does not affect served traffic until the draft\n is published. `files` is the complete bundle: add a file by uploading it with UploadSkillFile\n and including its id, remove one by leaving it out, and replace one by uploading a new file and\n referencing it at the same path. Published versions are never affected.\n\n### Allowed Auth Scopes\n\n- wilma-skills:UpdateSkill","parameters":[{"name":"skillId","in":"path","required":true,"schema":{"type":"string","maxLength":12,"minLength":12}},{"name":"company","in":"query","required":false,"schema":{"type":"string","maxLength":30,"minLength":3}},{"name":"user","in":"query","required":false,"schema":{"type":"string","maxLength":12,"minLength":12}}],"requestBody":{"required":true,"schema":{"type":"object","properties":{"description":{"type":"string","maxLength":1024,"description":"What the skill does and when to use it. Loaded at discovery time for progressive disclosure."},"content":{"type":"string","maxLength":204800,"minLength":1,"description":"The SKILL.md markdown body: the instructions themselves. Progressive disclosure: loaded when the\n skill is activated. Keep it concise and move detail into bundled files the agent reads on demand."},"tools":{"type":"array","items":{"description":"A tool a skill depends on. Uses the same references an agent uses in `engine.ai.capabilities`, so a tool is configured identically whether it hangs off an agent or off a skill.","oneOf":[{"type":"object","title":"tool","properties":{"tool":{"type":"object","properties":{"id":{"type":"string","maxLength":12,"minLength":12,"pattern":"^[0-9A-Za-z]{12}$","description":"Unique identifier of the tool using nanoid format"},"toolVersion":{"type":"number","minimum":1,"description":"Pin a specific published tool version. When omitted, the tool's latestVersion is resolved at load, so a republished tool reaches this agent without a new agent version. Pin it when `fields` must keep aiming at an exact input schema."},"description":{"type":"string","maxLength":4096,"minLength":1,"description":"Override the tool's description for this agent. When omitted, the tool's own description is used."},"fields":{"type":"array","items":{"type":"object","description":"Per-field override applied to a shared tool's input schema for this agent.","properties":{"path":{"type":"string","maxLength":512,"minLength":1,"pattern":"^/.*$","description":"JSON Pointer to the target property in the tool input schema."},"handler":{"oneOf":[{"type":"object","title":"guided","properties":{"guided":{"type":"object","description":"Keeps the field model-fillable but overrides its description in the schema shown to the model.","properties":{"description":{"type":"string","maxLength":1024,"minLength":1}},"required":["description"]}},"required":["guided"]},{"type":"object","title":"predefined","properties":{"predefined":{"type":"object","description":"A fixed value injected at the field path and excluded from the schema shown to the model, so the model neither sees nor sets it. A JSON value, or a string with handlebars templates resolved against agent variables and session context (e.g. '{{callerName}}').","properties":{"value":{}},"required":["value"]}},"required":["predefined"]}]}},"required":["handler","path"]},"description":"Per-field overrides on the tool input schema: pin fixed values (hidden from the model) or refine field descriptions. Fields not listed are filled by the model per the tool schema. Paths are resolved against the input schema of the tool version in effect — pin `toolVersion` when they must not move."},"startMessage":{"type":"string","description":"Message spoken when the tool call starts. Honoured on the voice channel. Spoken in every language of the call while `startMessages` is absent or empty; not consulted once `startMessages` carries any entry."},"startMessages":{"type":"array","items":{"type":"object","description":"One phrase in one language, for a capability that speaks a fixed line on the voice channel.\n Which entry is spoken is decided by the call's current language — the one the call resolved\n to at connect, or the one automatic detection switched to — matched on `language`\n CASE-INSENSITIVELY, the same rule `VoiceBotSpokenPhrase` states for the legacy stack, so a\n code written in either case reaches the same entry on either. Blank `text` means the agent\n says nothing in that language; it does NOT fall back to the language-agnostic member beside\n the list, which would speak one language's text in another language's voice.","properties":{"language":{"type":"string","maxLength":8,"minLength":2,"pattern":"^[a-z]{2,3}(-[A-Z]{2})?$","description":"BCP-47 language tag, optionally with a region subtag: 'en', 'en-US', 'pt-BR'. Deliberately a\n constrained string rather than an enum: the offerable set is a property of the selected\n recogniser and changes with it, so an enumeration here would cap the set at whatever was known\n when the model was released."},"text":{"type":"string"}},"required":["language","text"]},"description":"Per-language start messages. When present and non-empty, the entry for the call's current\n language is spoken and `startMessage` is not consulted; a language with no entry is silent.\n When absent or empty, `startMessage` is spoken as before. On a call with no effective speech\n configuration — no configured languages, or a PBX below protocol 2 — `startMessage` is spoken\n regardless, because there is no current language to choose by."},"pipeline":{"type":"object","properties":{"type":{"type":"string","description":"How the model waits for a tool and whether it synthesizes a reply from the result.","enum":["blocking_request","blocking_request_guided","async_request","async_request_guided"]},"instructions":{"type":"string","description":"Reply instructions for the *_GUIDED pipeline types. Use {{response}} to reference the tool result."}}},"approval":{"type":"object","description":"Human-in-the-loop confirmation for a tool. Presence requires approval: the agent pauses before running the tool and the session user (whoever controls the session) approves or denies inline.","properties":{"prompt":{"type":"string","description":"Optional message shown in the confirmation prompt. Supports handlebars against agent variables and the pending tool arguments. When omitted, a default prompt is shown."}}},"channels":{"type":"array","items":{"type":"string","description":"A channel an agent can serve (see AgentChannels for enablement). Also the runtime channel recorded by the channel runtimes / wilma-traces. A background task has no delivery surface and therefore no channel.","enum":["voice","chat","assistant"]},"uniqueItems":true,"description":"Channels this capability is offered on. Absent or empty — the default — means every channel the agent serves\n that can honour it. Listing channels narrows that set and never widens it: a `transfer` listed for `chat` is\n still ignored there, because the chat channel has no call to transfer."},"annotations":{"type":"array","items":{"description":"Configuration for annotations emitted after a tool completes.","oneOf":[{"type":"object","title":"tag","properties":{"tag":{"type":"object","description":"Tag annotation emitted after a tool completes.","properties":{"value":{"type":"string","description":"Static handlebars expression resolved against agent variables and helpers."},"applyOnSuccess":{"type":"boolean","description":"Apply when the tool succeeds."},"applyOnFailure":{"type":"boolean","description":"Apply when the tool fails."}}}},"required":["tag"]},{"type":"object","title":"comment","properties":{"comment":{"type":"object","description":"Comment annotation emitted after a tool completes.","properties":{"value":{"type":"string","description":"Static handlebars expression resolved against agent variables and helpers."},"applyOnSuccess":{"type":"boolean","description":"Apply when the tool succeeds."},"applyOnFailure":{"type":"boolean","description":"Apply when the tool fails."}}}},"required":["comment"]},{"type":"object","title":"variables","properties":{"variables":{"type":"object","description":"Variables annotation emitted after a tool completes."}},"required":["variables"]},{"type":"object","title":"result","properties":{"result":{"type":"object","description":"Result annotation emitted after a tool completes."}},"required":["result"]}]}}},"required":["id"]}},"required":["tool"]},{"type":"object","title":"connector","properties":{"connector":{"type":"object","description":"A configured connector (wilma-tools) exposed to the model as a single meta-tool named\n after the connector. The model calls the reserved `help` action to learn the callable\n actions, then dispatches an action through the same tool. Progressive disclosure runs\n through the connector tool itself, not the skills catalogue.","properties":{"id":{"type":"string","maxLength":12,"minLength":12,"pattern":"^[0-9A-Za-z]{12}$","description":"Configured connector instance in wilma-tools. Its pinned definition, permission `grant` and authorization govern what the model can discover via `help` and call. There is no version pin here, unlike `AgentTool.toolVersion`: the action catalogue and its schemas are already frozen by the connector's pinned definition, while `grant` and authorization are read live so permission changes apply at once."},"startMessage":{"type":"string","description":"Message spoken when a connector action call starts. Honoured on the voice channel. Mirrors `AgentTool.startMessage`. Not applied to the read-only `help` action. Not consulted once `startMessages` carries any entry."},"startMessages":{"type":"array","items":{"type":"object","description":"One phrase in one language, for a capability that speaks a fixed line on the voice channel.\n Which entry is spoken is decided by the call's current language — the one the call resolved\n to at connect, or the one automatic detection switched to — matched on `language`\n CASE-INSENSITIVELY, the same rule `VoiceBotSpokenPhrase` states for the legacy stack, so a\n code written in either case reaches the same entry on either. Blank `text` means the agent\n says nothing in that language; it does NOT fall back to the language-agnostic member beside\n the list, which would speak one language's text in another language's voice.","properties":{"language":{"type":"string","maxLength":8,"minLength":2,"pattern":"^[a-z]{2,3}(-[A-Z]{2})?$","description":"BCP-47 language tag, optionally with a region subtag: 'en', 'en-US', 'pt-BR'. Deliberately a\n constrained string rather than an enum: the offerable set is a property of the selected\n recogniser and changes with it, so an enumeration here would cap the set at whatever was known\n when the model was released."},"text":{"type":"string"}},"required":["language","text"]},"description":"Forward-compatible per-language connector start messages, mirroring `AgentTool.startMessages`. Accepted and stored so clients can configure connector and tool phrases consistently, but currently ignored at execution time because the voice runtime does not read connector start messages yet. Runtime enablement is tracked in wilma-services docs/todo.md item 8."},"pipeline":{"type":"object","properties":{"type":{"type":"string","description":"How the model waits for a tool and whether it synthesizes a reply from the result.","enum":["blocking_request","blocking_request_guided","async_request","async_request_guided"]},"instructions":{"type":"string","description":"Reply instructions for the *_GUIDED pipeline types. Use {{response}} to reference the tool result."}}},"approval":{"type":"object","description":"Human-in-the-loop confirmation for a tool. Presence requires approval: the agent pauses before running the tool and the session user (whoever controls the session) approves or denies inline.","properties":{"prompt":{"type":"string","description":"Optional message shown in the confirmation prompt. Supports handlebars against agent variables and the pending tool arguments. When omitted, a default prompt is shown."}}},"channels":{"type":"array","items":{"type":"string","description":"A channel an agent can serve (see AgentChannels for enablement). Also the runtime channel recorded by the channel runtimes / wilma-traces. A background task has no delivery surface and therefore no channel.","enum":["voice","chat","assistant"]},"uniqueItems":true,"description":"Channels this capability is offered on. Absent or empty — the default — means every channel the agent serves\n that can honour it. Listing channels narrows that set and never widens it: a `transfer` listed for `chat` is\n still ignored there, because the chat channel has no call to transfer."},"annotations":{"type":"array","items":{"description":"Configuration for annotations emitted after a tool completes.","oneOf":[{"type":"object","title":"tag","properties":{"tag":{"type":"object","description":"Tag annotation emitted after a tool completes.","properties":{"value":{"type":"string","description":"Static handlebars expression resolved against agent variables and helpers."},"applyOnSuccess":{"type":"boolean","description":"Apply when the tool succeeds."},"applyOnFailure":{"type":"boolean","description":"Apply when the tool fails."}}}},"required":["tag"]},{"type":"object","title":"comment","properties":{"comment":{"type":"object","description":"Comment annotation emitted after a tool completes.","properties":{"value":{"type":"string","description":"Static handlebars expression resolved against agent variables and helpers."},"applyOnSuccess":{"type":"boolean","description":"Apply when the tool succeeds."},"applyOnFailure":{"type":"boolean","description":"Apply when the tool fails."}}}},"required":["comment"]},{"type":"object","title":"variables","properties":{"variables":{"type":"object","description":"Variables annotation emitted after a tool completes."}},"required":["variables"]},{"type":"object","title":"result","properties":{"result":{"type":"object","description":"Result annotation emitted after a tool completes."}},"required":["result"]}]}}},"required":["id"]}},"required":["connector"]},{"type":"object","title":"kb","properties":{"kb":{"type":"object","properties":{"knowledgeBaseId":{"type":"string"},"instructions":{"type":"string","maxLength":60000,"minLength":1,"description":"Additional instructions for when the agent should use this knowledge base."},"searchStrategy":{"type":"string","description":"Search algorithm used to retrieve passages.","enum":["bm25","vector","hybrid"]},"searchThreshold":{"type":"string","description":"Minimum similarity a passage needs to be returned. Raising it trades recall for precision.","enum":["low","medium","high"]},"searchLimit":{"type":"number","maximum":50,"minimum":1,"description":"Maximum number of passages this capability returns. Defaults to the knowledge base service's own default (10) when omitted. Keep it low on voice: every passage is prompt the model reads before it can speak."},"channels":{"type":"array","items":{"type":"string","description":"A channel an agent can serve (see AgentChannels for enablement). Also the runtime channel recorded by the channel runtimes / wilma-traces. A background task has no delivery surface and therefore no channel.","enum":["voice","chat","assistant"]},"uniqueItems":true,"description":"Channels this capability is offered on. Absent or empty — the default — means every channel the agent serves\n that can honour it. Listing channels narrows that set and never widens it: a `transfer` listed for `chat` is\n still ignored there, because the chat channel has no call to transfer."},"annotations":{"type":"array","items":{"description":"Configuration for annotations emitted after a tool completes.","oneOf":[{"type":"object","title":"tag","properties":{"tag":{"type":"object","description":"Tag annotation emitted after a tool completes.","properties":{"value":{"type":"string","description":"Static handlebars expression resolved against agent variables and helpers."},"applyOnSuccess":{"type":"boolean","description":"Apply when the tool succeeds."},"applyOnFailure":{"type":"boolean","description":"Apply when the tool fails."}}}},"required":["tag"]},{"type":"object","title":"comment","properties":{"comment":{"type":"object","description":"Comment annotation emitted after a tool completes.","properties":{"value":{"type":"string","description":"Static handlebars expression resolved against agent variables and helpers."},"applyOnSuccess":{"type":"boolean","description":"Apply when the tool succeeds."},"applyOnFailure":{"type":"boolean","description":"Apply when the tool fails."}}}},"required":["comment"]},{"type":"object","title":"variables","properties":{"variables":{"type":"object","description":"Variables annotation emitted after a tool completes."}},"required":["variables"]},{"type":"object","title":"result","properties":{"result":{"type":"object","description":"Result annotation emitted after a tool completes."}},"required":["result"]}]}}}}},"required":["kb"]},{"type":"object","title":"webSearch","properties":{"webSearch":{"type":"object","description":"Model-native web search. The provider tool is selected from the agent model.","properties":{"contextSize":{"type":"string","description":"Search depth/context hint, mapped per provider.","enum":["low","medium","high"]},"allowedDomains":{"type":"array","items":{"type":"string","maxLength":253,"minLength":1},"description":"Restrict results to these domains where the provider supports it."},"userLocation":{"type":"object","properties":{"country":{"type":"string","description":"ISO 3166-1 alpha-2 country code, e.g. 'US'."},"region":{"type":"string"},"city":{"type":"string"},"timezone":{"type":"string","description":"IANA timezone, e.g. 'Europe/Rome'."}}},"channels":{"type":"array","items":{"type":"string","description":"A channel an agent can serve (see AgentChannels for enablement). Also the runtime channel recorded by the channel runtimes / wilma-traces. A background task has no delivery surface and therefore no channel.","enum":["voice","chat","assistant"]},"uniqueItems":true,"description":"Channels this capability is offered on. Absent or empty — the default — means every channel the agent serves\n that can honour it. Listing channels narrows that set and never widens it: a `transfer` listed for `chat` is\n still ignored there, because the chat channel has no call to transfer."}}}},"required":["webSearch"]},{"type":"object","title":"webFetch","properties":{"webFetch":{"type":"object","description":"Server-side fetch of a URL, returning its readable text/markdown to the model.","properties":{"allowedDomains":{"type":"array","items":{"type":"string","maxLength":253,"minLength":1},"description":"Restrict fetches to these domains. Empty list means any domain is allowed."},"channels":{"type":"array","items":{"type":"string","description":"A channel an agent can serve (see AgentChannels for enablement). Also the runtime channel recorded by the channel runtimes / wilma-traces. A background task has no delivery surface and therefore no channel.","enum":["voice","chat","assistant"]},"uniqueItems":true,"description":"Channels this capability is offered on. Absent or empty — the default — means every channel the agent serves\n that can honour it. Listing channels narrows that set and never widens it: a `transfer` listed for `chat` is\n still ignored there, because the chat channel has no call to transfer."}}}},"required":["webFetch"]},{"type":"object","title":"transfer","properties":{"transfer":{"type":"object","description":"Transfer the call to a PBX destination. Honoured on the voice channel. One capability entry per transfer target.","properties":{"name":{"type":"string","description":"Tool name exposed to the model. Auto-generated when omitted."},"description":{"type":"string","description":"Description for the model to decide when to transfer."},"variant":{"type":"string","enum":["predefined","dynamic","directory"]},"context":{"type":"string","description":"Routing context for PREDEFINED transfers."},"extension":{"type":"string","description":"Extension or phone number for PREDEFINED transfers."},"pipeline":{"type":"object","properties":{"type":{"type":"string","enum":["transfer","reply_and_transfer","reply_instructed_and_transfer"]},"instructions":{"type":"string","description":"Instructions for the model reply when type is REPLY_INSTRUCTED_AND_TRANSFER."}}},"channels":{"type":"array","items":{"type":"string","description":"A channel an agent can serve (see AgentChannels for enablement). Also the runtime channel recorded by the channel runtimes / wilma-traces. A background task has no delivery surface and therefore no channel.","enum":["voice","chat","assistant"]},"uniqueItems":true,"description":"Channels this capability is offered on. Absent or empty — the default — means every channel the agent serves\n that can honour it. Listing channels narrows that set and never widens it: a `transfer` listed for `chat` is\n still ignored there, because the chat channel has no call to transfer."},"annotations":{"type":"array","items":{"description":"Configuration for annotations emitted after a tool completes.","oneOf":[{"type":"object","title":"tag","properties":{"tag":{"type":"object","description":"Tag annotation emitted after a tool completes.","properties":{"value":{"type":"string","description":"Static handlebars expression resolved against agent variables and helpers."},"applyOnSuccess":{"type":"boolean","description":"Apply when the tool succeeds."},"applyOnFailure":{"type":"boolean","description":"Apply when the tool fails."}}}},"required":["tag"]},{"type":"object","title":"comment","properties":{"comment":{"type":"object","description":"Comment annotation emitted after a tool completes.","properties":{"value":{"type":"string","description":"Static handlebars expression resolved against agent variables and helpers."},"applyOnSuccess":{"type":"boolean","description":"Apply when the tool succeeds."},"applyOnFailure":{"type":"boolean","description":"Apply when the tool fails."}}}},"required":["comment"]},{"type":"object","title":"variables","properties":{"variables":{"type":"object","description":"Variables annotation emitted after a tool completes."}},"required":["variables"]},{"type":"object","title":"result","properties":{"result":{"type":"object","description":"Result annotation emitted after a tool completes."}},"required":["result"]}]}}}}},"required":["transfer"]},{"type":"object","title":"hangup","properties":{"hangup":{"type":"object","description":"Let the model end the call. Honoured on the voice channel.","properties":{"name":{"type":"string","description":"Tool name exposed to the model. Auto-generated when omitted."},"description":{"type":"string","description":"Description for the model to decide when to hang up."},"pipeline":{"type":"object","properties":{"type":{"type":"string","enum":["hangup","reply_and_hangup","reply_instructed_and_hangup"]},"instructions":{"type":"string","description":"Instructions for the model reply when type is REPLY_INSTRUCTED_AND_HANGUP."}}},"channels":{"type":"array","items":{"type":"string","description":"A channel an agent can serve (see AgentChannels for enablement). Also the runtime channel recorded by the channel runtimes / wilma-traces. A background task has no delivery surface and therefore no channel.","enum":["voice","chat","assistant"]},"uniqueItems":true,"description":"Channels this capability is offered on. Absent or empty — the default — means every channel the agent serves\n that can honour it. Listing channels narrows that set and never widens it: a `transfer` listed for `chat` is\n still ignored there, because the chat channel has no call to transfer."},"annotations":{"type":"array","items":{"description":"Configuration for annotations emitted after a tool completes.","oneOf":[{"type":"object","title":"tag","properties":{"tag":{"type":"object","description":"Tag annotation emitted after a tool completes.","properties":{"value":{"type":"string","description":"Static handlebars expression resolved against agent variables and helpers."},"applyOnSuccess":{"type":"boolean","description":"Apply when the tool succeeds."},"applyOnFailure":{"type":"boolean","description":"Apply when the tool fails."}}}},"required":["tag"]},{"type":"object","title":"comment","properties":{"comment":{"type":"object","description":"Comment annotation emitted after a tool completes.","properties":{"value":{"type":"string","description":"Static handlebars expression resolved against agent variables and helpers."},"applyOnSuccess":{"type":"boolean","description":"Apply when the tool succeeds."},"applyOnFailure":{"type":"boolean","description":"Apply when the tool fails."}}}},"required":["comment"]},{"type":"object","title":"variables","properties":{"variables":{"type":"object","description":"Variables annotation emitted after a tool completes."}},"required":["variables"]},{"type":"object","title":"result","properties":{"result":{"type":"object","description":"Result annotation emitted after a tool completes."}},"required":["result"]}]}}}}},"required":["hangup"]},{"type":"object","title":"wait","properties":{"wait":{"type":"object","description":"Let the model hold the call while it works (e.g. a lookup), keeping the caller on the line. Honoured on the voice channel.","properties":{"name":{"type":"string","description":"Tool name exposed to the model. Auto-generated when omitted."},"description":{"type":"string","description":"Description for the model to decide when to wait."},"message":{"type":"string","description":"Message spoken when entering the wait state. Spoken in every language of the call while `messages` is absent or empty; not consulted once `messages` carries any entry."},"messages":{"type":"array","items":{"type":"object","description":"One phrase in one language, for a capability that speaks a fixed line on the voice channel.\n Which entry is spoken is decided by the call's current language — the one the call resolved\n to at connect, or the one automatic detection switched to — matched on `language`\n CASE-INSENSITIVELY, the same rule `VoiceBotSpokenPhrase` states for the legacy stack, so a\n code written in either case reaches the same entry on either. Blank `text` means the agent\n says nothing in that language; it does NOT fall back to the language-agnostic member beside\n the list, which would speak one language's text in another language's voice.","properties":{"language":{"type":"string","maxLength":8,"minLength":2,"pattern":"^[a-z]{2,3}(-[A-Z]{2})?$","description":"BCP-47 language tag, optionally with a region subtag: 'en', 'en-US', 'pt-BR'. Deliberately a\n constrained string rather than an enum: the offerable set is a property of the selected\n recogniser and changes with it, so an enumeration here would cap the set at whatever was known\n when the model was released."},"text":{"type":"string"}},"required":["language","text"]},"description":"Per-language wait messages. Same rule as `AgentTool.startMessages`: the entry for the call's current language is spoken, a language with no entry is silent, and with no effective speech configuration `message` is spoken."},"channels":{"type":"array","items":{"type":"string","description":"A channel an agent can serve (see AgentChannels for enablement). Also the runtime channel recorded by the channel runtimes / wilma-traces. A background task has no delivery surface and therefore no channel.","enum":["voice","chat","assistant"]},"uniqueItems":true,"description":"Channels this capability is offered on. Absent or empty — the default — means every channel the agent serves\n that can honour it. Listing channels narrows that set and never widens it: a `transfer` listed for `chat` is\n still ignored there, because the chat channel has no call to transfer."},"annotations":{"type":"array","items":{"description":"Configuration for annotations emitted after a tool completes.","oneOf":[{"type":"object","title":"tag","properties":{"tag":{"type":"object","description":"Tag annotation emitted after a tool completes.","properties":{"value":{"type":"string","description":"Static handlebars expression resolved against agent variables and helpers."},"applyOnSuccess":{"type":"boolean","description":"Apply when the tool succeeds."},"applyOnFailure":{"type":"boolean","description":"Apply when the tool fails."}}}},"required":["tag"]},{"type":"object","title":"comment","properties":{"comment":{"type":"object","description":"Comment annotation emitted after a tool completes.","properties":{"value":{"type":"string","description":"Static handlebars expression resolved against agent variables and helpers."},"applyOnSuccess":{"type":"boolean","description":"Apply when the tool succeeds."},"applyOnFailure":{"type":"boolean","description":"Apply when the tool fails."}}}},"required":["comment"]},{"type":"object","title":"variables","properties":{"variables":{"type":"object","description":"Variables annotation emitted after a tool completes."}},"required":["variables"]},{"type":"object","title":"result","properties":{"result":{"type":"object","description":"Result annotation emitted after a tool completes."}},"required":["result"]}]}}}}},"required":["wait"]}]},"description":"Tools the skill's instructions depend on, merged into the agent's toolset while the skill is active."},"files":{"type":"array","items":{"type":"object","description":"A file bundled with a skill: a reference to immutable uploaded content, plus the path it is materialised at.","properties":{"id":{"type":"string","maxLength":12,"minLength":12,"pattern":"^[A-Za-z0-9_-]{12}$","description":"Uploaded content this entry points at, as returned by UploadSkillFile."},"path":{"type":"string","maxLength":1024,"minLength":1,"pattern":"^[a-zA-Z0-9](?:[a-zA-Z0-9._/-]*[a-zA-Z0-9_])?$","description":"Path the file is materialised at inside the bundle."},"name":{"type":"string","maxLength":255,"minLength":1,"description":"File name shown to the user. Defaults to the name recorded at upload."},"size":{"type":"number","description":"Size in bytes. Ignored on input; the service reports the size of the stored content."},"executable":{"type":"boolean","description":"Marks the file executable when it is materialised in the sandbox. Set per skill, not per upload."}},"required":["id","path"]},"maxItems":200,"description":"Files bundled with the skill: references to uploaded content (UploadSkillFile) placed at bundle\n paths. The list is the complete bundle — a file left out of an update is no longer part of the\n skill, and changing a file means uploading a new one and referencing it at the same path."},"name":{"type":"string","maxLength":255,"minLength":1,"description":"The skill's name as people see it: free text, editable, not unique. The identifier the model\n loads a skill by is not stored — it is a slug derived from this name when an agent's tools are\n built for a session, transliterated and numbered (`booking`, `booking-2`) so it is unique among\n the skills attached to that agent. Renaming a skill changes that slug and nothing else."},"revision":{"type":"number","description":"Draft revision this update is based on (optimistic locking). When it does not match the current revision, RevisionConflictException is returned."},"access":{"type":"object","description":"Who can use and edit the skill. Usage for private skills is limited to the creator, editors and viewers; company skills are attachable by anyone in the organization.","properties":{"visibility":{"type":"string","enum":["private","company"]},"editors":{"type":"array","items":{"description":"A user or group granted edit access to the skill draft.","oneOf":[{"type":"object","title":"userId","properties":{"userId":{"type":"string","maxLength":12,"minLength":12}},"required":["userId"]},{"type":"object","title":"groupId","properties":{"groupId":{"type":"string"}},"required":["groupId"]}]},"description":"Users and groups that may edit the draft. The creator always has edit access."},"viewers":{"type":"array","items":{"description":"A user or group granted edit access to the skill draft.","oneOf":[{"type":"object","title":"userId","properties":{"userId":{"type":"string","maxLength":12,"minLength":12}},"required":["userId"]},{"type":"object","title":"groupId","properties":{"groupId":{"type":"string"}},"required":["groupId"]}]},"description":"Users and groups the skill is shared with: they may see and attach it, but not edit. Redundant under company visibility."}}}},"required":["content","description","name","revision"]}}},"delete":{"operationId":"DeleteSkill","summary":"","description":"Deletes a skill, its bundled files and version history. Agents that still reference the skill keep the reference as a soft link: they run without it, the runtime skips the attachment, and their session traces record the missing skill id (`skill.missingIds`). Query wilma-audit references first to see who still names it.\n\n### Allowed Auth Scopes\n\n- wilma-skills:DeleteSkill","parameters":[{"name":"skillId","in":"path","required":true,"schema":{"type":"string","maxLength":12,"minLength":12}},{"name":"company","in":"query","required":false,"schema":{"type":"string","maxLength":30,"minLength":3}},{"name":"user","in":"query","required":false,"schema":{"type":"string","maxLength":12,"minLength":12}}]}},"/v2/skills/items/{skillId}/compare-versions":{"get":{"operationId":"CompareSkillVersions","summary":"","description":"Returns the field-level differences between two versions, or between a version and the current draft.\n\n### Allowed Auth Scopes\n\n- wilma-skills:CompareSkillVersions\n- wilma-skills:read","parameters":[{"name":"skillId","in":"path","required":true,"schema":{"type":"string","maxLength":12,"minLength":12}},{"name":"company","in":"query","required":false,"schema":{"type":"string","maxLength":30,"minLength":3}},{"name":"from","in":"query","required":false,"schema":{"type":"number","minimum":1,"description":"Base published version number. Omit when fromDraft is true."},"description":"Base published version number. Omit when fromDraft is true."},{"name":"fromDraft","in":"query","required":false,"schema":{"type":"boolean","description":"Use the current draft as the base instead of a published version."},"description":"Use the current draft as the base instead of a published version."},{"name":"to","in":"query","required":false,"schema":{"type":"number","minimum":1,"description":"Target published version number. Omit when toDraft is true."},"description":"Target published version number. Omit when toDraft is true."},{"name":"toDraft","in":"query","required":false,"schema":{"type":"boolean","description":"Compare against the current draft instead of a published version."},"description":"Compare against the current draft instead of a published version."}]}},"/v2/skills/items/{skillId}/versions":{"get":{"operationId":"ListSkillVersions","summary":"","description":"Lists the published versions of a skill, newest first.\n\n### Allowed Auth Scopes\n\n- wilma-skills:ListSkillVersions\n- wilma-skills:read","parameters":[{"name":"skillId","in":"path","required":true,"schema":{"type":"string","maxLength":12,"minLength":12}},{"name":"company","in":"query","required":false,"schema":{"type":"string","maxLength":30,"minLength":3}},{"name":"limit","in":"query","required":false,"schema":{"type":"number","maximum":100,"minimum":1}},{"name":"cursor","in":"query","required":false,"schema":{"type":"string","maxLength":2048,"minLength":1,"description":"Opaque cursor returned by a previous page; pass it back to fetch the next page."},"description":"Opaque cursor returned by a previous page; pass it back to fetch the next page."}]},"post":{"operationId":"PublishSkillVersion","summary":"","description":"Publishes the current draft as a new immutable version and updates latestVersion. Unpinned agent attachments resolve latestVersion on their next load.\n\n### Allowed Auth Scopes\n\n- wilma-skills:PublishSkillVersion","parameters":[{"name":"skillId","in":"path","required":true,"schema":{"type":"string","maxLength":12,"minLength":12}},{"name":"company","in":"query","required":false,"schema":{"type":"string","maxLength":30,"minLength":3}}],"requestBody":{"required":true,"schema":{"type":"object","properties":{"changelog":{"type":"string","maxLength":500,"minLength":1,"description":"What changed in this version."},"revision":{"type":"number","description":"Draft revision this publish is based on (optimistic locking)."}},"required":["revision"]}}}},"/v2/skills/items/{skillId}/versions/{version}":{"get":{"operationId":"GetSkillVersion","summary":"","description":"Retrieves a published skill version, including its full immutable definition snapshot.\n\n### Allowed Auth Scopes\n\n- wilma-skills:GetSkillVersion\n- wilma-skills:read","parameters":[{"name":"skillId","in":"path","required":true,"schema":{"type":"string","maxLength":12,"minLength":12}},{"name":"version","in":"path","required":true,"schema":{"type":"number","minimum":1,"description":"Published skill version number. Monotonic within a skill's version history, starting at 1."},"description":"Published skill version number. Monotonic within a skill's version history, starting at 1."},{"name":"company","in":"query","required":false,"schema":{"type":"string","maxLength":30,"minLength":3}}]}},"/v2/skills/items/{skillId}/versions/{version}/draft":{"post":{"operationId":"RestoreSkillVersionToDraft","summary":"","description":"Replaces the mutable draft with a published version's definition. The draft revision is incremented; published versions and latestVersion are unchanged until the draft is published.\n\n### Allowed Auth Scopes\n\n- wilma-skills:RestoreSkillVersionToDraft","parameters":[{"name":"skillId","in":"path","required":true,"schema":{"type":"string","maxLength":12,"minLength":12}},{"name":"version","in":"path","required":true,"schema":{"type":"number","minimum":1,"description":"Published skill version number. Monotonic within a skill's version history, starting at 1."},"description":"Published skill version number. Monotonic within a skill's version history, starting at 1."},{"name":"company","in":"query","required":false,"schema":{"type":"string","maxLength":30,"minLength":3}}],"requestBody":{"required":true,"schema":{"type":"object","properties":{"revision":{"type":"number","description":"Draft revision this restore is based on (optimistic locking)."}},"required":["revision"]}}}},"/v2/tools":{"get":{"operationId":"ListTools","summary":"","description":"List all tools for the current company.\n\n### Allowed Auth Scopes\n\n- tools:ListTools\n- tools:read","parameters":[{"name":"company","in":"query","required":false,"schema":{"type":"string","maxLength":30,"minLength":3,"description":"The unique identifier of the tenant when a service token is used."},"description":"The unique identifier of the tenant when a service token is used."},{"name":"status","in":"query","required":false,"schema":{"type":"string","enum":["draft","active"]},"description":"Only tools with this version state."},{"name":"search","in":"query","required":false,"schema":{"type":"string","description":"Filter by name, category or description."},"description":"Filter by name, category or description."},{"name":"limit","in":"query","required":false,"schema":{"type":"number","maximum":100,"minimum":1}},{"name":"cursor","in":"query","required":false,"schema":{"type":"string","maxLength":2048,"minLength":1,"description":"Opaque cursor returned by a previous page; pass it back to fetch the next page."},"description":"Opaque cursor returned by a previous page; pass it back to fetch the next page."}]},"post":{"operationId":"CreateTool","summary":"","description":"Create a new tool draft. The tool is offered to no model until PublishToolVersion snapshots the draft into its first version.\n\n### Allowed Auth Scopes\n\n- tools:CreateTool","parameters":[{"name":"company","in":"query","required":false,"schema":{"type":"string","maxLength":30,"minLength":3,"description":"The unique identifier of the tenant when a service token is used."},"description":"The unique identifier of the tenant when a service token is used."}],"requestBody":{"required":true,"schema":{"type":"object","description":"The complete, versionable configuration of a tool. This is what gets snapshotted when a version is published.","properties":{"name":{"type":"string","maxLength":128,"minLength":1,"pattern":"^[a-zA-Z0-9_-]+$","description":"Name of the tool, e.g.: 'send_email'"},"category":{"type":"string","maxLength":128,"minLength":1,"description":"Category of the tool, e.g.: 'Support'"},"description":{"type":"string","maxLength":4096,"minLength":1,"description":"Description of the tool for people and AI. Optional; can be overridden per agent via AgentTool.description."},"input":{"type":"object","properties":{"schema":{"description":"JSON Schema (2020-12) describing the tool arguments. The model is offered this schema (minus any agent-predefined fields); ExecuteTool input is validated against it."}},"required":["schema"]},"handler":{"description":"Handler of the tool. Every tool executes through one of three mechanisms: a pinned connector definition (built-in providers such as email, webhook, search, chat, sms, calendar — and external integrations — share the same contract), an MCP server registration, or a single tool on a registered MCP server.","oneOf":[{"type":"object","title":"connector","properties":{"connector":{"type":"object","description":"Handler for a connector-backed Tool created through `CreateTool`. Carries two\nconfiguration layers: `configuration` (from the mixin) holds definition-level values for\nthe connector as a whole (validated against the definition `configuration.schema`), while\n`toolConfiguration` holds values specific to this created Tool (validated against the\npinned connector tool's `configuration.schema`).\n\n`Tool.input` follows copy-at-create semantics: when `input` is omitted on create, the\nservice snapshots the pinned connector tool's input schema into the Tool; callers may\ninstead provide — and later edit — a custom schema. The Tool owns its copy; definition\nchanges never propagate to existing tools. Reshaping the input of a tool whose handler\ndoes not consume input through configuration templates fails at execution, not at write.","properties":{"definition":{"type":"object","description":"Reference to one immutable connector definition.","properties":{"key":{"type":"string","maxLength":128,"minLength":1,"pattern":"^[a-z][a-z0-9._-]*$","description":"Stable definition identifier."},"version":{"type":"string","maxLength":64,"minLength":1,"description":"Exact definition version."}},"required":["key","version"]},"configuration":{"description":"Values validated against the definition configuration schema."},"authorization":{"description":"Credential selection for a configured connector. Credentials remain in wilma-secrets.","oneOf":[{"type":"object","title":"shared","properties":{"shared":{"type":"object","description":"Company-managed connection resolved through wilma-secrets.","properties":{"connection":{"type":"object","description":"A reference to a connection, held by other services in place of the credential. Resolved to a\nshort-lived token server-to-server via GetConnectionToken.","properties":{"id":{"type":"string","maxLength":8,"minLength":8,"pattern":"^[0-9A-Za-z]{8}$","description":"Unique identifier of a configured connection."}},"required":["id"]}},"required":["connection"]}},"required":["shared"]},{"type":"object","title":"user","properties":{"user":{"type":"object","description":"Marker selecting per-user authorization. Carries no connection reference: at execution the\nexecuting user's own credential for this connector's provider is resolved through\nwilma-secrets (GetUserConnectionToken by provider + user)."}},"required":["user"]}]},"toolId":{"type":"string","maxLength":256,"minLength":1,"pattern":"^[a-zA-Z0-9._:-]+$","description":"Connector tool from the pinned definition executed by this Tool."},"toolConfiguration":{"description":"Values validated against the pinned connector tool's per-tool configuration schema\n(`ConnectorTool.configuration.schema`). Omitted when that tool declares no per-tool\nconfiguration contract."}},"required":["configuration","definition","toolId"]}},"required":["connector"]},{"type":"object","title":"mcp","properties":{"mcp":{"type":"object","properties":{"serverUrl":{"type":"string","description":"MCP server endpoint URL"},"authorization":{"oneOf":[{"type":"object","title":"connection","properties":{"connection":{"type":"object","description":"A reference to a connection, held by other services in place of the credential. Resolved to a\nshort-lived token server-to-server via GetConnectionToken.","properties":{"id":{"type":"string","maxLength":8,"minLength":8,"pattern":"^[0-9A-Za-z]{8}$","description":"Unique identifier of a configured connection."}},"required":["id"]}},"required":["connection"]},{"type":"object","title":"customHeaders","properties":{"customHeaders":{"type":"object","additionalProperties":{"type":"string"},"propertyNames":{"type":"string"}}},"required":["customHeaders"]},{"type":"object","title":"apiKey","properties":{"apiKey":{"type":"object","description":"A SecretRef plus the header it travels under. The referenced secret is expected to carry\n variant `auth_api_key`; the value is resolved server-to-server at execution and never stored\n on the tool.","properties":{"secret":{"type":"object","description":"A reference to a stored secret, held by other services in place of the value.\nResolved server-to-server via GetSecretValue at execution time — always to the current value.","properties":{"id":{"type":"string","maxLength":8,"minLength":8,"pattern":"^[0-9A-Za-z]{8}$","description":"Unique identifier of a secret."}},"required":["id"]},"headerName":{"type":"string","maxLength":128,"minLength":1,"pattern":"^[!#$%&'*+.^_`|~0-9A-Za-z-]+$","description":"Header the key is sent under, e.g. `X-API-Key` or `PRIVATE-TOKEN`."},"prefix":{"type":"string","description":"Prepended to the value verbatim, for the rare API wanting `Token abc123`."}},"required":["headerName","secret"]}},"required":["apiKey"]},{"type":"object","title":"user","properties":{"user":{"type":"object","description":"Marker selecting per-user MCP authorization. Carries no reference: the credential is keyed by server and user."}},"required":["user"]}]},"tools":{"type":"array","items":{"type":"string","maxLength":128,"minLength":1,"pattern":"^[a-zA-Z0-9_-]+$","description":"Name of the tool, e.g.: 'send_email'"},"description":"Tools enabled for this MCP server."}},"required":["serverUrl","tools"]}},"required":["mcp"]},{"type":"object","title":"mcpTool","properties":{"mcpTool":{"type":"object","properties":{"mcpToolId":{"type":"string","maxLength":12,"minLength":12,"pattern":"^[0-9A-Za-z]{12}$","description":"ID of the Tool that represents the MCP server connection (the Tool with handler.mcp containing serverUrl and authorization)"},"mcpToolName":{"type":"string","description":"Name of the tool on the MCP server. This matches the tool name returned from DiscoverTools operation."}},"required":["mcpToolId","mcpToolName"]}},"required":["mcpTool"]}]}},"required":["category","handler","name"]}}}},"/v2/tools/capabilities/describe":{"post":{"operationId":"DescribeCapabilities","summary":"","description":"Describes an agent's wilma-tools-backed capabilities — standalone Tools and configured\nConnectors — in a single request; callers pass only `companyId` and capability refs.\n\n### Allowed Auth Scopes\n\n- tools:DescribeCapabilities\n- tools:read","parameters":[{"name":"company","in":"query","required":false,"schema":{"type":"string","maxLength":30,"minLength":3,"description":"The unique identifier of the tenant when a service token is used."},"description":"The unique identifier of the tenant when a service token is used."}],"requestBody":{"required":true,"schema":{"type":"object","properties":{"capabilities":{"type":"array","items":{"description":"Reference to one wilma-tools-backed agent capability to describe: either a standalone\nTool or a configured Connector.\n\nThe two members are deliberately asymmetric. A Tool is a model-facing contract — its input\nschema is what the model fills, and agents pin JSON Pointers into it — so it is versioned and\nreferenced by `ToolRef`. A Connector exposes no schema at reference time: the model discovers\nactions at runtime through the reserved `help` action, and every action, schema and document it\ncan reach is already frozen by the connector's pinned `ConnectorDefinitionRef`. A configured\nconnector therefore needs no version history of its own and is referenced by id.","oneOf":[{"type":"object","title":"tool","properties":{"tool":{"type":"object","description":"Reference to one tool, optionally pinned to a published version. When `version` is omitted the tool's latestVersion is resolved.","properties":{"id":{"type":"string","maxLength":12,"minLength":12,"pattern":"^[0-9A-Za-z]{12}$","description":"Unique identifier of the tool using nanoid format"},"version":{"type":"number","minimum":1,"description":"Pin a specific published tool version. When omitted, latestVersion is resolved."}},"required":["id"]}},"required":["tool"]},{"type":"object","title":"connector","properties":{"connector":{"type":"string","maxLength":12,"minLength":12,"pattern":"^[0-9A-Za-z]{12}$","description":"Configured connector instance identifier."}},"required":["connector"]}]},"description":"Tool and connector references to describe."}},"required":["capabilities"]}}}},"/v2/tools/connector-definitions":{"get":{"operationId":"ListConnectorDefinitions","summary":"","description":"Lists immutable connector definitions available from the server catalog.\n\n### Allowed Auth Scopes\n\n- tools:ListConnectorDefinitions\n- tools:read","parameters":[{"name":"company","in":"query","required":false,"schema":{"type":"string","maxLength":30,"minLength":3,"description":"The unique identifier of the tenant when a service token is used."},"description":"The unique identifier of the tenant when a service token is used."},{"name":"query","in":"query","required":false,"schema":{"type":"string","description":"Case-insensitive filter applied to connector titles, keys, and descriptions."},"description":"Case-insensitive filter applied to connector titles, keys, and descriptions."},{"name":"limit","in":"query","required":false,"schema":{"type":"number","maximum":100,"minimum":1}},{"name":"cursor","in":"query","required":false,"schema":{"type":"string","maxLength":2048,"minLength":1,"description":"Opaque cursor returned by a previous page; pass it back to fetch the next page."},"description":"Opaque cursor returned by a previous page; pass it back to fetch the next page."}]}},"/v2/tools/connector-definitions/{key}/versions/{version}":{"get":{"operationId":"GetConnectorDefinition","summary":"","description":"Gets one immutable connector definition.\n\n### Allowed Auth Scopes\n\n- tools:GetConnectorDefinition\n- tools:read","parameters":[{"name":"key","in":"path","required":true,"schema":{"type":"string","maxLength":128,"minLength":1,"pattern":"^[a-z][a-z0-9._-]*$","description":"Stable connector definition key."},"description":"Stable connector definition key."},{"name":"version","in":"path","required":true,"schema":{"type":"string","maxLength":64,"minLength":1,"description":"Exact immutable definition version."},"description":"Exact immutable definition version."},{"name":"company","in":"query","required":false,"schema":{"type":"string","maxLength":30,"minLength":3,"description":"The unique identifier of the tenant when a service token is used."},"description":"The unique identifier of the tenant when a service token is used."}]}},"/v2/tools/connector-definitions/{key}/versions/{version}/configuration":{"post":{"operationId":"ResolveConnectorConfiguration","summary":"","description":"Resolves account-dependent configuration fields from current form values. By default the\ndefinition-level configuration contract is resolved; pass `toolId` to resolve a connector\ntool's per-tool configuration contract instead.\n\n### Allowed Auth Scopes\n\n- tools:ResolveConnectorConfiguration\n- tools:read","parameters":[{"name":"key","in":"path","required":true,"schema":{"type":"string","maxLength":128,"minLength":1,"pattern":"^[a-z][a-z0-9._-]*$","description":"Stable connector definition key."},"description":"Stable connector definition key."},{"name":"version","in":"path","required":true,"schema":{"type":"string","maxLength":64,"minLength":1,"description":"Exact immutable definition version."},"description":"Exact immutable definition version."},{"name":"company","in":"query","required":false,"schema":{"type":"string","maxLength":30,"minLength":3,"description":"The unique identifier of the tenant when a service token is used."},"description":"The unique identifier of the tenant when a service token is used."},{"name":"toolId","in":"query","required":false,"schema":{"type":"string","maxLength":256,"minLength":1,"pattern":"^[a-zA-Z0-9._:-]+$","description":"When set, resolve the per-tool configuration contract of this connector tool\n(`ConnectorTool.configuration`) instead of the definition-level configuration."},"description":"When set, resolve the per-tool configuration contract of this connector tool\n(`ConnectorTool.configuration`) instead of the definition-level configuration."}],"requestBody":{"required":true,"schema":{"type":"object","properties":{"values":{"description":"Current partial form values."},"authorization":{"description":"Credential selection for a configured connector. Credentials remain in wilma-secrets.","oneOf":[{"type":"object","title":"shared","properties":{"shared":{"type":"object","description":"Company-managed connection resolved through wilma-secrets.","properties":{"connection":{"type":"object","description":"A reference to a connection, held by other services in place of the credential. Resolved to a\nshort-lived token server-to-server via GetConnectionToken.","properties":{"id":{"type":"string","maxLength":8,"minLength":8,"pattern":"^[0-9A-Za-z]{8}$","description":"Unique identifier of a configured connection."}},"required":["id"]}},"required":["connection"]}},"required":["shared"]},{"type":"object","title":"user","properties":{"user":{"type":"object","description":"Marker selecting per-user authorization. Carries no connection reference: at execution the\nexecuting user's own credential for this connector's provider is resolved through\nwilma-secrets (GetUserConnectionToken by provider + user)."}},"required":["user"]}]},"user":{"type":"string","maxLength":256,"minLength":1,"description":"Executing user when user authorization is selected."}},"required":["values"]}}}},"/v2/tools/connectors":{"get":{"operationId":"tools.ListConnectors","summary":"","description":"Lists configured connectors owned by the current company.\n\n### Allowed Auth Scopes\n\n- tools:ListConnectors\n- tools:read","parameters":[{"name":"company","in":"query","required":false,"schema":{"type":"string","maxLength":30,"minLength":3,"description":"The unique identifier of the tenant when a service token is used."},"description":"The unique identifier of the tenant when a service token is used."},{"name":"limit","in":"query","required":false,"schema":{"type":"number","maximum":100,"minimum":1}},{"name":"cursor","in":"query","required":false,"schema":{"type":"string","maxLength":2048,"minLength":1,"description":"Opaque cursor returned by a previous page; pass it back to fetch the next page."},"description":"Opaque cursor returned by a previous page; pass it back to fetch the next page."}]},"post":{"operationId":"CreateConnector","summary":"","description":"Creates a company-owned connector from a server definition.\n\n### Allowed Auth Scopes\n\n- tools:CreateConnector","parameters":[{"name":"company","in":"query","required":false,"schema":{"type":"string","maxLength":30,"minLength":3,"description":"The unique identifier of the tenant when a service token is used."},"description":"The unique identifier of the tenant when a service token is used."}],"requestBody":{"required":true,"schema":{"type":"object","description":"Fields supplied when a connector is created or replaced.","properties":{"definition":{"type":"object","description":"Reference to one immutable connector definition.","properties":{"key":{"type":"string","maxLength":128,"minLength":1,"pattern":"^[a-z][a-z0-9._-]*$","description":"Stable definition identifier."},"version":{"type":"string","maxLength":64,"minLength":1,"description":"Exact definition version."}},"required":["key","version"]},"configuration":{"description":"Values validated against the definition configuration schema."},"authorization":{"description":"Credential selection for a configured connector. Credentials remain in wilma-secrets.","oneOf":[{"type":"object","title":"shared","properties":{"shared":{"type":"object","description":"Company-managed connection resolved through wilma-secrets.","properties":{"connection":{"type":"object","description":"A reference to a connection, held by other services in place of the credential. Resolved to a\nshort-lived token server-to-server via GetConnectionToken.","properties":{"id":{"type":"string","maxLength":8,"minLength":8,"pattern":"^[0-9A-Za-z]{8}$","description":"Unique identifier of a configured connection."}},"required":["id"]}},"required":["connection"]}},"required":["shared"]},{"type":"object","title":"user","properties":{"user":{"type":"object","description":"Marker selecting per-user authorization. Carries no connection reference: at execution the\nexecuting user's own credential for this connector's provider is resolved through\nwilma-secrets (GetUserConnectionToken by provider + user)."}},"required":["user"]}]},"name":{"type":"string","maxLength":128,"minLength":1,"pattern":"^[a-zA-Z0-9_-]+$","description":"Name exposed to administrators and AI agents."},"description":{"type":"string","maxLength":4096,"minLength":1,"description":"Description exposed to administrators and AI agents."},"grant":{"description":"Coarse permission grant for a configured connector. The service computes the effective,\nserver-enforced tool set by filtering the pinned definition's tools with this grant (using\neach tool's `ConnectorToolAccess`), and requests only the scopes the grant implies at\nconnection time.","oneOf":[{"type":"object","title":"read","properties":{"read":{"type":"object","description":"Marker: grant every READ tool."}},"required":["read"]},{"type":"object","title":"readWrite","properties":{"readWrite":{"type":"object","description":"Marker: grant every READ and WRITE tool."}},"required":["readWrite"]},{"type":"object","title":"all","properties":{"all":{"type":"object","description":"Marker: grant every tool, including DESTRUCTIVE."}},"required":["all"]},{"type":"object","title":"tools","properties":{"tools":{"type":"array","items":{"type":"string","maxLength":256,"minLength":1,"pattern":"^[a-zA-Z0-9._:-]+$","description":"Stable identifier of a tool within a connector definition."},"description":"Advanced: an explicit allowlist of tool ids, regardless of access class."}},"required":["tools"]}]}},"required":["configuration","definition","description","grant","name"]}}}},"/v2/tools/connectors/describe":{"post":{"operationId":"DescribeConnectors","summary":"","description":"Describes configured connectors by id for agent load; callers pass only `companyId` and\nthe connector ids. Returns the compact data needed to register each connector's meta-tool.\n\n### Allowed Auth Scopes\n\n- tools:DescribeConnectors\n- tools:read","parameters":[{"name":"company","in":"query","required":false,"schema":{"type":"string","maxLength":30,"minLength":3,"description":"The unique identifier of the tenant when a service token is used."},"description":"The unique identifier of the tenant when a service token is used."}],"requestBody":{"required":true,"schema":{"type":"object","properties":{"ids":{"type":"array","items":{"type":"string","maxLength":12,"minLength":12,"pattern":"^[0-9A-Za-z]{12}$","description":"Unique identifier of a configured connector."},"description":"Configured connector instance identifiers."}},"required":["ids"]}}}},"/v2/tools/connectors/items/{connectorId}":{"get":{"operationId":"GetConnector","summary":"","description":"Gets a configured connector.\n\n### Allowed Auth Scopes\n\n- tools:GetConnector\n- tools:read","parameters":[{"name":"connectorId","in":"path","required":true,"schema":{"type":"string","maxLength":12,"minLength":12,"pattern":"^[0-9A-Za-z]{12}$","description":"Connector instance identifier."},"description":"Connector instance identifier."},{"name":"company","in":"query","required":false,"schema":{"type":"string","maxLength":30,"minLength":3,"description":"The unique identifier of the tenant when a service token is used."},"description":"The unique identifier of the tenant when a service token is used."}]},"put":{"operationId":"UpdateConnector","summary":"","description":"Replaces the editable configuration of a connector.\n\n### Allowed Auth Scopes\n\n- tools:UpdateConnector","parameters":[{"name":"connectorId","in":"path","required":true,"schema":{"type":"string","maxLength":12,"minLength":12,"pattern":"^[0-9A-Za-z]{12}$","description":"Connector instance identifier."},"description":"Connector instance identifier."},{"name":"company","in":"query","required":false,"schema":{"type":"string","maxLength":30,"minLength":3,"description":"The unique identifier of the tenant when a service token is used."},"description":"The unique identifier of the tenant when a service token is used."}],"requestBody":{"required":true,"schema":{"type":"object","description":"Fields supplied when a connector is created or replaced.","properties":{"definition":{"type":"object","description":"Reference to one immutable connector definition.","properties":{"key":{"type":"string","maxLength":128,"minLength":1,"pattern":"^[a-z][a-z0-9._-]*$","description":"Stable definition identifier."},"version":{"type":"string","maxLength":64,"minLength":1,"description":"Exact definition version."}},"required":["key","version"]},"configuration":{"description":"Values validated against the definition configuration schema."},"authorization":{"description":"Credential selection for a configured connector. Credentials remain in wilma-secrets.","oneOf":[{"type":"object","title":"shared","properties":{"shared":{"type":"object","description":"Company-managed connection resolved through wilma-secrets.","properties":{"connection":{"type":"object","description":"A reference to a connection, held by other services in place of the credential. Resolved to a\nshort-lived token server-to-server via GetConnectionToken.","properties":{"id":{"type":"string","maxLength":8,"minLength":8,"pattern":"^[0-9A-Za-z]{8}$","description":"Unique identifier of a configured connection."}},"required":["id"]}},"required":["connection"]}},"required":["shared"]},{"type":"object","title":"user","properties":{"user":{"type":"object","description":"Marker selecting per-user authorization. Carries no connection reference: at execution the\nexecuting user's own credential for this connector's provider is resolved through\nwilma-secrets (GetUserConnectionToken by provider + user)."}},"required":["user"]}]},"name":{"type":"string","maxLength":128,"minLength":1,"pattern":"^[a-zA-Z0-9_-]+$","description":"Name exposed to administrators and AI agents."},"description":{"type":"string","maxLength":4096,"minLength":1,"description":"Description exposed to administrators and AI agents."},"grant":{"description":"Coarse permission grant for a configured connector. The service computes the effective,\nserver-enforced tool set by filtering the pinned definition's tools with this grant (using\neach tool's `ConnectorToolAccess`), and requests only the scopes the grant implies at\nconnection time.","oneOf":[{"type":"object","title":"read","properties":{"read":{"type":"object","description":"Marker: grant every READ tool."}},"required":["read"]},{"type":"object","title":"readWrite","properties":{"readWrite":{"type":"object","description":"Marker: grant every READ and WRITE tool."}},"required":["readWrite"]},{"type":"object","title":"all","properties":{"all":{"type":"object","description":"Marker: grant every tool, including DESTRUCTIVE."}},"required":["all"]},{"type":"object","title":"tools","properties":{"tools":{"type":"array","items":{"type":"string","maxLength":256,"minLength":1,"pattern":"^[a-zA-Z0-9._:-]+$","description":"Stable identifier of a tool within a connector definition."},"description":"Advanced: an explicit allowlist of tool ids, regardless of access class."}},"required":["tools"]}]},"revision":{"type":"number","description":"Connector revision this update is based on (optimistic locking). When it does not match the\ncurrent revision, RevisionConflictException is returned."}},"required":["configuration","definition","description","grant","name","revision"]}}},"delete":{"operationId":"DeleteConnector","summary":"","description":"Deletes a configured connector. Fails with ConflictException while the connector is referenced\nby any agent or skill (see the wilma-audit QueryReferences operation for who references it).\n\n### Allowed Auth Scopes\n\n- tools:DeleteConnector","parameters":[{"name":"connectorId","in":"path","required":true,"schema":{"type":"string","maxLength":12,"minLength":12,"pattern":"^[0-9A-Za-z]{12}$","description":"Connector instance identifier."},"description":"Connector instance identifier."},{"name":"company","in":"query","required":false,"schema":{"type":"string","maxLength":30,"minLength":3,"description":"The unique identifier of the tenant when a service token is used."},"description":"The unique identifier of the tenant when a service token is used."}]}},"/v2/tools/connectors/items/{connectorId}/execute":{"post":{"operationId":"ExecuteConnector","summary":"","description":"Executes a connector with the invocation payload supplied by the model or agent.\n\n### Allowed Auth Scopes\n\n- tools:ExecuteConnector","parameters":[{"name":"connectorId","in":"path","required":true,"schema":{"type":"string","maxLength":12,"minLength":12,"pattern":"^[0-9A-Za-z]{12}$","description":"Connector instance identifier."},"description":"Connector instance identifier."},{"name":"company","in":"query","required":false,"schema":{"type":"string","maxLength":30,"minLength":3,"description":"The unique identifier of the tenant when a service token is used."},"description":"The unique identifier of the tenant when a service token is used."}],"requestBody":{"required":true,"schema":{"type":"object","properties":{"input":{"description":"Invocation payload from the model or agent. For multi-tool connectors this\nis typically an object with exactly one connector tool id key and argument object.\n\n`help` is a reserved action returning guidance in `result.output`; nothing external is\ndispatched, and a definition must not declare a tool with id `help`:\n - `{\"help\": {}}` — index: the connector's `instructions`, the enabled actions grouped\n by `category` (id, name, one-line description, access), and the list of reference\n documents (name + summary).\n - `{\"help\": {\"action\": \"<toolId>\"}}` — detail for one action: full input schema,\n `examples`, and `docs`.\n - `{\"help\": {\"reference\": \"<name>\"}}` — the full body of one reference document.\n - `{\"help\": {\"category\": \"<name>\"}}` — details for every action in a category."},"service":{"type":"string","maxLength":256,"minLength":1,"description":"Service executing the connector, such as `chatbots` or `voicebots`."},"user":{"type":"string","maxLength":256,"minLength":1,"description":"User identity required by user-authorized connectors."}},"required":["input"]}}}},"/v2/tools/describe":{"post":{"operationId":"DescribeTools","summary":"","description":"Describe multiple tools for agent load. Each reference either pins a published version or resolves the tool's latestVersion; the resolved version is reported on every description. A tool with no published version is not describable.\n\n### Allowed Auth Scopes\n\n- tools:DescribeTools\n- tools:read","parameters":[{"name":"company","in":"query","required":false,"schema":{"type":"string","maxLength":30,"minLength":3,"description":"The unique identifier of the tenant when a service token is used."},"description":"The unique identifier of the tenant when a service token is used."}],"requestBody":{"required":true,"schema":{"type":"object","properties":{"tools":{"type":"array","items":{"type":"object","description":"Reference to one tool, optionally pinned to a published version. When `version` is omitted the tool's latestVersion is resolved.","properties":{"id":{"type":"string","maxLength":12,"minLength":12,"pattern":"^[0-9A-Za-z]{12}$","description":"Unique identifier of the tool using nanoid format"},"version":{"type":"number","minimum":1,"description":"Pin a specific published tool version. When omitted, latestVersion is resolved."}},"required":["id"]},"description":"Tools to describe, each optionally pinned to a published version."}},"required":["tools"]}}}},"/v2/tools/discover":{"post":{"operationId":"DiscoverTools","summary":"","description":"Discover tools from an MCP server.\n\n### Allowed Auth Scopes\n\n- tools:DiscoverTools\n- tools:read","parameters":[{"name":"company","in":"query","required":false,"schema":{"type":"string","maxLength":30,"minLength":3,"description":"The unique identifier of the tenant when a service token is used."},"description":"The unique identifier of the tenant when a service token is used."}],"requestBody":{"required":true,"schema":{"type":"object","properties":{"serverUrl":{"type":"string","description":"MCP server endpoint URL"},"authorization":{"oneOf":[{"type":"object","title":"connection","properties":{"connection":{"type":"object","description":"A reference to a connection, held by other services in place of the credential. Resolved to a\nshort-lived token server-to-server via GetConnectionToken.","properties":{"id":{"type":"string","maxLength":8,"minLength":8,"pattern":"^[0-9A-Za-z]{8}$","description":"Unique identifier of a configured connection."}},"required":["id"]}},"required":["connection"]},{"type":"object","title":"customHeaders","properties":{"customHeaders":{"type":"object","additionalProperties":{"type":"string"},"propertyNames":{"type":"string"}}},"required":["customHeaders"]},{"type":"object","title":"apiKey","properties":{"apiKey":{"type":"object","description":"A SecretRef plus the header it travels under. The referenced secret is expected to carry\n variant `auth_api_key`; the value is resolved server-to-server at execution and never stored\n on the tool.","properties":{"secret":{"type":"object","description":"A reference to a stored secret, held by other services in place of the value.\nResolved server-to-server via GetSecretValue at execution time — always to the current value.","properties":{"id":{"type":"string","maxLength":8,"minLength":8,"pattern":"^[0-9A-Za-z]{8}$","description":"Unique identifier of a secret."}},"required":["id"]},"headerName":{"type":"string","maxLength":128,"minLength":1,"pattern":"^[!#$%&'*+.^_`|~0-9A-Za-z-]+$","description":"Header the key is sent under, e.g. `X-API-Key` or `PRIVATE-TOKEN`."},"prefix":{"type":"string","description":"Prepended to the value verbatim, for the rare API wanting `Token abc123`."}},"required":["headerName","secret"]}},"required":["apiKey"]},{"type":"object","title":"user","properties":{"user":{"type":"object","description":"Marker selecting per-user MCP authorization. Carries no reference: the credential is keyed by server and user."}},"required":["user"]}]}},"required":["serverUrl"]}}}},"/v2/tools/execute":{"post":{"operationId":"ExecuteTool","summary":"","description":"Execute a tool with the provided input.\n\n### Allowed Auth Scopes\n\n- tools:ExecuteTool","parameters":[{"name":"company","in":"query","required":false,"schema":{"type":"string","maxLength":30,"minLength":3,"description":"The unique identifier of the tenant when a service token is used."},"description":"The unique identifier of the tenant when a service token is used."}],"requestBody":{"required":true,"schema":{"type":"object","properties":{"id":{"type":"string","maxLength":12,"minLength":12,"pattern":"^[0-9A-Za-z]{12}$","description":"Unique identifier of the tool using nanoid format"},"version":{"type":"number","minimum":1,"description":"Published tool version to execute. Mutually exclusive with draft. Pass the `version` reported by DescribeTools so the call runs against the definition the model was shown; when omitted, latestVersion is resolved at execution time."},"draft":{"type":"boolean","description":"Run the tool's current draft (for testing). Mutually exclusive with version.\n\n The draft is still never *offered* to a model — DescribeTools reports a tool with no\n published version as unresolvable either way — so only a caller that names this flag can\n reach one. It selects which definition executes and nothing else: a draft run of a write or\n destructive tool has exactly the effect a published run would, and is audited the same."},"input":{"description":"Tool arguments, validated against the input schema of the executed version."},"service":{"type":"string","maxLength":256,"minLength":1,"description":"Service executing the tool, e.g. 'chatbots'."},"user":{"type":"string","maxLength":256,"minLength":1,"description":"User for whom the tool is executed, e.g. 'john.doe@example.com'."}},"required":["id","input"]}}}},"/v2/tools/items/{toolId}":{"get":{"operationId":"GetTool","summary":"","description":"Get a tool's draft by its ID, including its version state. Use GetToolVersion to read a published version's definition.\n\n### Allowed Auth Scopes\n\n- tools:GetTool\n- tools:read","parameters":[{"name":"toolId","in":"path","required":true,"schema":{"type":"string","maxLength":12,"minLength":12,"pattern":"^[0-9A-Za-z]{12}$","description":"Unique identifier of the tool using nanoid format"},"description":"Unique identifier of the tool using nanoid format"},{"name":"company","in":"query","required":false,"schema":{"type":"string","maxLength":30,"minLength":3,"description":"The unique identifier of the tenant when a service token is used."},"description":"The unique identifier of the tenant when a service token is used."}]},"put":{"operationId":"UpdateTool","summary":"","description":"Update the draft definition of a tool. Published versions are never affected, so nothing the model sees changes until the draft is published.\n\n### Allowed Auth Scopes\n\n- tools:UpdateTool","parameters":[{"name":"toolId","in":"path","required":true,"schema":{"type":"string","maxLength":12,"minLength":12,"pattern":"^[0-9A-Za-z]{12}$","description":"Unique identifier of the tool using nanoid format"},"description":"Unique identifier of the tool using nanoid format"},{"name":"company","in":"query","required":false,"schema":{"type":"string","maxLength":30,"minLength":3,"description":"The unique identifier of the tenant when a service token is used."},"description":"The unique identifier of the tenant when a service token is used."}],"requestBody":{"required":true,"schema":{"type":"object","description":"The complete, versionable configuration of a tool. This is what gets snapshotted when a version is published.","properties":{"name":{"type":"string","maxLength":128,"minLength":1,"pattern":"^[a-zA-Z0-9_-]+$","description":"Name of the tool, e.g.: 'send_email'"},"category":{"type":"string","maxLength":128,"minLength":1,"description":"Category of the tool, e.g.: 'Support'"},"description":{"type":"string","maxLength":4096,"minLength":1,"description":"Description of the tool for people and AI. Optional; can be overridden per agent via AgentTool.description."},"input":{"type":"object","properties":{"schema":{"description":"JSON Schema (2020-12) describing the tool arguments. The model is offered this schema (minus any agent-predefined fields); ExecuteTool input is validated against it."}},"required":["schema"]},"handler":{"description":"Handler of the tool. Every tool executes through one of three mechanisms: a pinned connector definition (built-in providers such as email, webhook, search, chat, sms, calendar — and external integrations — share the same contract), an MCP server registration, or a single tool on a registered MCP server.","oneOf":[{"type":"object","title":"connector","properties":{"connector":{"type":"object","description":"Handler for a connector-backed Tool created through `CreateTool`. Carries two\nconfiguration layers: `configuration` (from the mixin) holds definition-level values for\nthe connector as a whole (validated against the definition `configuration.schema`), while\n`toolConfiguration` holds values specific to this created Tool (validated against the\npinned connector tool's `configuration.schema`).\n\n`Tool.input` follows copy-at-create semantics: when `input` is omitted on create, the\nservice snapshots the pinned connector tool's input schema into the Tool; callers may\ninstead provide — and later edit — a custom schema. The Tool owns its copy; definition\nchanges never propagate to existing tools. Reshaping the input of a tool whose handler\ndoes not consume input through configuration templates fails at execution, not at write.","properties":{"definition":{"type":"object","description":"Reference to one immutable connector definition.","properties":{"key":{"type":"string","maxLength":128,"minLength":1,"pattern":"^[a-z][a-z0-9._-]*$","description":"Stable definition identifier."},"version":{"type":"string","maxLength":64,"minLength":1,"description":"Exact definition version."}},"required":["key","version"]},"configuration":{"description":"Values validated against the definition configuration schema."},"authorization":{"description":"Credential selection for a configured connector. Credentials remain in wilma-secrets.","oneOf":[{"type":"object","title":"shared","properties":{"shared":{"type":"object","description":"Company-managed connection resolved through wilma-secrets.","properties":{"connection":{"type":"object","description":"A reference to a connection, held by other services in place of the credential. Resolved to a\nshort-lived token server-to-server via GetConnectionToken.","properties":{"id":{"type":"string","maxLength":8,"minLength":8,"pattern":"^[0-9A-Za-z]{8}$","description":"Unique identifier of a configured connection."}},"required":["id"]}},"required":["connection"]}},"required":["shared"]},{"type":"object","title":"user","properties":{"user":{"type":"object","description":"Marker selecting per-user authorization. Carries no connection reference: at execution the\nexecuting user's own credential for this connector's provider is resolved through\nwilma-secrets (GetUserConnectionToken by provider + user)."}},"required":["user"]}]},"toolId":{"type":"string","maxLength":256,"minLength":1,"pattern":"^[a-zA-Z0-9._:-]+$","description":"Connector tool from the pinned definition executed by this Tool."},"toolConfiguration":{"description":"Values validated against the pinned connector tool's per-tool configuration schema\n(`ConnectorTool.configuration.schema`). Omitted when that tool declares no per-tool\nconfiguration contract."}},"required":["configuration","definition","toolId"]}},"required":["connector"]},{"type":"object","title":"mcp","properties":{"mcp":{"type":"object","properties":{"serverUrl":{"type":"string","description":"MCP server endpoint URL"},"authorization":{"oneOf":[{"type":"object","title":"connection","properties":{"connection":{"type":"object","description":"A reference to a connection, held by other services in place of the credential. Resolved to a\nshort-lived token server-to-server via GetConnectionToken.","properties":{"id":{"type":"string","maxLength":8,"minLength":8,"pattern":"^[0-9A-Za-z]{8}$","description":"Unique identifier of a configured connection."}},"required":["id"]}},"required":["connection"]},{"type":"object","title":"customHeaders","properties":{"customHeaders":{"type":"object","additionalProperties":{"type":"string"},"propertyNames":{"type":"string"}}},"required":["customHeaders"]},{"type":"object","title":"apiKey","properties":{"apiKey":{"type":"object","description":"A SecretRef plus the header it travels under. The referenced secret is expected to carry\n variant `auth_api_key`; the value is resolved server-to-server at execution and never stored\n on the tool.","properties":{"secret":{"type":"object","description":"A reference to a stored secret, held by other services in place of the value.\nResolved server-to-server via GetSecretValue at execution time — always to the current value.","properties":{"id":{"type":"string","maxLength":8,"minLength":8,"pattern":"^[0-9A-Za-z]{8}$","description":"Unique identifier of a secret."}},"required":["id"]},"headerName":{"type":"string","maxLength":128,"minLength":1,"pattern":"^[!#$%&'*+.^_`|~0-9A-Za-z-]+$","description":"Header the key is sent under, e.g. `X-API-Key` or `PRIVATE-TOKEN`."},"prefix":{"type":"string","description":"Prepended to the value verbatim, for the rare API wanting `Token abc123`."}},"required":["headerName","secret"]}},"required":["apiKey"]},{"type":"object","title":"user","properties":{"user":{"type":"object","description":"Marker selecting per-user MCP authorization. Carries no reference: the credential is keyed by server and user."}},"required":["user"]}]},"tools":{"type":"array","items":{"type":"string","maxLength":128,"minLength":1,"pattern":"^[a-zA-Z0-9_-]+$","description":"Name of the tool, e.g.: 'send_email'"},"description":"Tools enabled for this MCP server."}},"required":["serverUrl","tools"]}},"required":["mcp"]},{"type":"object","title":"mcpTool","properties":{"mcpTool":{"type":"object","properties":{"mcpToolId":{"type":"string","maxLength":12,"minLength":12,"pattern":"^[0-9A-Za-z]{12}$","description":"ID of the Tool that represents the MCP server connection (the Tool with handler.mcp containing serverUrl and authorization)"},"mcpToolName":{"type":"string","description":"Name of the tool on the MCP server. This matches the tool name returned from DiscoverTools operation."}},"required":["mcpToolId","mcpToolName"]}},"required":["mcpTool"]}]},"revision":{"type":"number","description":"Draft revision this update is based on (optimistic locking). When it does not match the current revision, RevisionConflictException is returned."}},"required":["category","handler","name","revision"]}}},"delete":{"operationId":"DeleteTool","summary":"","description":"Delete a tool, its draft and its version history. Fails with ConflictException while the tool is referenced by any agent or skill (see the wilma-audit QueryReferences operation for who references it).\n\n### Allowed Auth Scopes\n\n- tools:DeleteTool","parameters":[{"name":"toolId","in":"path","required":true,"schema":{"type":"string","maxLength":12,"minLength":12,"pattern":"^[0-9A-Za-z]{12}$","description":"Unique identifier of the tool using nanoid format"},"description":"Unique identifier of the tool using nanoid format"},{"name":"company","in":"query","required":false,"schema":{"type":"string","maxLength":30,"minLength":3,"description":"The unique identifier of the tenant when a service token is used."},"description":"The unique identifier of the tenant when a service token is used."}]}},"/v2/tools/items/{toolId}/compare-versions":{"get":{"operationId":"CompareToolVersions","summary":"","description":"Returns the field-level differences between two versions, or between a version and the current draft.\n\n### Allowed Auth Scopes\n\n- tools:CompareToolVersions\n- tools:read","parameters":[{"name":"toolId","in":"path","required":true,"schema":{"type":"string","maxLength":12,"minLength":12,"pattern":"^[0-9A-Za-z]{12}$","description":"Unique identifier of the tool using nanoid format"},"description":"Unique identifier of the tool using nanoid format"},{"name":"company","in":"query","required":false,"schema":{"type":"string","maxLength":30,"minLength":3,"description":"The unique identifier of the tenant when a service token is used."},"description":"The unique identifier of the tenant when a service token is used."},{"name":"from","in":"query","required":false,"schema":{"type":"number","minimum":1,"description":"Base published version number. Omit when fromDraft is true."},"description":"Base published version number. Omit when fromDraft is true."},{"name":"fromDraft","in":"query","required":false,"schema":{"type":"boolean","description":"Use the current draft as the base instead of a published version."},"description":"Use the current draft as the base instead of a published version."},{"name":"to","in":"query","required":false,"schema":{"type":"number","minimum":1,"description":"Target published version number. Omit when toDraft is true."},"description":"Target published version number. Omit when toDraft is true."},{"name":"toDraft","in":"query","required":false,"schema":{"type":"boolean","description":"Compare against the current draft instead of a published version."},"description":"Compare against the current draft instead of a published version."}]}},"/v2/tools/items/{toolId}/versions":{"get":{"operationId":"ListToolVersions","summary":"","description":"Lists the published versions of a tool, newest first.\n\n### Allowed Auth Scopes\n\n- tools:ListToolVersions\n- tools:read","parameters":[{"name":"toolId","in":"path","required":true,"schema":{"type":"string","maxLength":12,"minLength":12,"pattern":"^[0-9A-Za-z]{12}$","description":"Unique identifier of the tool using nanoid format"},"description":"Unique identifier of the tool using nanoid format"},{"name":"company","in":"query","required":false,"schema":{"type":"string","maxLength":30,"minLength":3,"description":"The unique identifier of the tenant when a service token is used."},"description":"The unique identifier of the tenant when a service token is used."},{"name":"limit","in":"query","required":false,"schema":{"type":"number","maximum":100,"minimum":1}},{"name":"cursor","in":"query","required":false,"schema":{"type":"string","maxLength":2048,"minLength":1,"description":"Opaque cursor returned by a previous page; pass it back to fetch the next page."},"description":"Opaque cursor returned by a previous page; pass it back to fetch the next page."}]},"post":{"operationId":"PublishToolVersion","summary":"","description":"Publishes the current draft as a new immutable version and updates latestVersion. Agent and skill capabilities that omit a version pin resolve latestVersion on their next load.\n\n### Allowed Auth Scopes\n\n- tools:PublishToolVersion","parameters":[{"name":"toolId","in":"path","required":true,"schema":{"type":"string","maxLength":12,"minLength":12,"pattern":"^[0-9A-Za-z]{12}$","description":"Unique identifier of the tool using nanoid format"},"description":"Unique identifier of the tool using nanoid format"},{"name":"company","in":"query","required":false,"schema":{"type":"string","maxLength":30,"minLength":3,"description":"The unique identifier of the tenant when a service token is used."},"description":"The unique identifier of the tenant when a service token is used."}],"requestBody":{"required":true,"schema":{"type":"object","properties":{"changelog":{"type":"string","maxLength":500,"minLength":1,"description":"What changed in this version."},"revision":{"type":"number","description":"Draft revision this publish is based on (optimistic locking)."}},"required":["revision"]}}}},"/v2/tools/items/{toolId}/versions/{version}":{"get":{"operationId":"GetToolVersion","summary":"","description":"Retrieves a published tool version, including its full immutable definition snapshot.\n\n### Allowed Auth Scopes\n\n- tools:GetToolVersion\n- tools:read","parameters":[{"name":"toolId","in":"path","required":true,"schema":{"type":"string","maxLength":12,"minLength":12,"pattern":"^[0-9A-Za-z]{12}$","description":"Unique identifier of the tool using nanoid format"},"description":"Unique identifier of the tool using nanoid format"},{"name":"version","in":"path","required":true,"schema":{"type":"number","minimum":1,"description":"Published tool version number. Monotonic within a tool's version history, starting at 1."},"description":"Published tool version number. Monotonic within a tool's version history, starting at 1."},{"name":"company","in":"query","required":false,"schema":{"type":"string","maxLength":30,"minLength":3,"description":"The unique identifier of the tenant when a service token is used."},"description":"The unique identifier of the tenant when a service token is used."}]}},"/v2/tools/items/{toolId}/versions/{version}/draft":{"post":{"operationId":"RestoreToolVersionToDraft","summary":"","description":"Replaces the mutable draft with a published version's definition. The draft revision is incremented; published versions and latestVersion are unchanged until the draft is published.\n\n### Allowed Auth Scopes\n\n- tools:RestoreToolVersionToDraft","parameters":[{"name":"toolId","in":"path","required":true,"schema":{"type":"string","maxLength":12,"minLength":12,"pattern":"^[0-9A-Za-z]{12}$","description":"Unique identifier of the tool using nanoid format"},"description":"Unique identifier of the tool using nanoid format"},{"name":"version","in":"path","required":true,"schema":{"type":"number","minimum":1,"description":"Published tool version number. Monotonic within a tool's version history, starting at 1."},"description":"Published tool version number. Monotonic within a tool's version history, starting at 1."},{"name":"company","in":"query","required":false,"schema":{"type":"string","maxLength":30,"minLength":3,"description":"The unique identifier of the tenant when a service token is used."},"description":"The unique identifier of the tenant when a service token is used."}],"requestBody":{"required":true,"schema":{"type":"object","properties":{"revision":{"type":"number","description":"Draft revision this restore is based on (optimistic locking)."}},"required":["revision"]}}}},"/v2/tools/mentions/resolve":{"get":{"operationId":"ResolveMention","summary":"","description":"Reads the current content of one referenced resource for an agent. Service-only. With `user`, the source is read with that user's permissions; without it, with company-wide access. Fails with `ValidationException` when the URI names no resource kind that can be read, `NotFoundException` when the resource does not exist, `ForbiddenException` when the user, or the company without a user, may not read it, and `ServiceUnavailableException` when its source failed to answer and a later read may succeed.\n\n### Allowed Auth Scopes\n\n- tools:ResolveMention\n- tools:read","parameters":[{"name":"company","in":"query","required":false,"schema":{"type":"string","maxLength":30,"minLength":3,"description":"The unique identifier of the tenant when a service token is used."},"description":"The unique identifier of the tenant when a service token is used."},{"name":"uri","in":"query","required":true,"schema":{"type":"string","maxLength":512,"minLength":1,"pattern":"^[a-z][a-z0-9+.-]*://.+$","description":"Canonical URI identifying one entity on the Wilma platform, e.g. 'wilma://agents/UXDT1W9fI3sj'\n or 'wilma://skills/aB3dE5fG7hJ9'. The path names the owning service and entity kind. Opaque to\n the contracts that carry it; non-Wilma schemes (records of external systems) fit the same shape."},"description":"Canonical URI identifying one entity on the Wilma platform, e.g. 'wilma://agents/UXDT1W9fI3sj'\n or 'wilma://skills/aB3dE5fG7hJ9'. The path names the owning service and entity kind. Opaque to\n the contracts that carry it; non-Wilma schemes (records of external systems) fit the same shape."},{"name":"user","in":"query","required":false,"schema":{"type":"string","maxLength":12,"minLength":12,"description":"The user whose permissions apply. Omit for company-wide access."},"description":"The user whose permissions apply. Omit for company-wide access."}]}},"/v2/traces":{"get":{"operationId":"QueryTraces","summary":"","description":"Queries traces newest first. Filter by service, status, time range, text\n or indexed trace attributes. Results contain trace summaries without\n spans.\n\n### Allowed Auth Scopes\n\n- wilma-traces:QueryTraces\n- wilma-traces:read","parameters":[{"name":"company","in":"query","required":false,"schema":{"type":"string","maxLength":30,"minLength":3}},{"name":"service","in":"query","required":false,"schema":{"type":"string"}},{"name":"status","in":"query","required":false,"schema":{"type":"string","description":"Current resource state. Complete is not immutable: a long-lived trace may\n transition from complete back to pending when new work is appended, then\n complete again.","enum":["complete","complete_with_errors","error","pending"]},"description":"Current resource state. Complete is not immutable: a long-lived trace may\n transition from complete back to pending when new work is appended, then\n complete again."},{"name":"from","in":"query","required":false,"schema":{"type":"string","description":"Inclusive start of the window on the trace's startTime: an ISO-8601 instant with its\n offset (`2026-07-10T14:12:30.123Z`, `2026-07-10T16:12:30+02:00`) or a date `YYYY-MM-DD`,\n read as midnight UTC. Any other spelling, or a `from` later than `to`, is a 400."},"description":"Inclusive start of the window on the trace's startTime: an ISO-8601 instant with its\n offset (`2026-07-10T14:12:30.123Z`, `2026-07-10T16:12:30+02:00`) or a date `YYYY-MM-DD`,\n read as midnight UTC. Any other spelling, or a `from` later than `to`, is a 400."},{"name":"to","in":"query","required":false,"schema":{"type":"string","description":"Exclusive end of the window on the trace's startTime, in the spelling `from` takes: the\n window is `[from, to)`."},"description":"Exclusive end of the window on the trace's startTime, in the spelling `from` takes: the\n window is `[from, to)`."},{"name":"search","in":"query","required":false,"schema":{"type":"string","description":"Case-insensitive substring, at most 128 characters, of the trace id or one of these\n Trace.attributes (span and message content is not searched): session.id, session.traceId,\n user.id, user.name, model, tool.callId, call.id, call.caller.name, call.caller.number,\n call.dialed, agent.id, agent.name, automation.id, automation.name, task.id, tool.id,\n tool.name, skill.id, skill.name, sandbox.id, conversation.id, profile.id, profile.name.\n A longer term is a 400."},"description":"Case-insensitive substring, at most 128 characters, of the trace id or one of these\n Trace.attributes (span and message content is not searched): session.id, session.traceId,\n user.id, user.name, model, tool.callId, call.id, call.caller.name, call.caller.number,\n call.dialed, agent.id, agent.name, automation.id, automation.name, task.id, tool.id,\n tool.name, skill.id, skill.name, sandbox.id, conversation.id, profile.id, profile.name.\n A longer term is a 400."},{"name":"limit","in":"query","required":false,"schema":{"type":"number","maximum":100,"minimum":1}},{"name":"cursor","in":"query","required":false,"schema":{"type":"string","maxLength":2048,"minLength":1,"description":"Opaque cursor returned by a previous page; pass it back to fetch the next page."},"description":"Opaque cursor returned by a previous page; pass it back to fetch the next page."},{"name":"filter","in":"query","required":false,"schema":{"type":"array","items":{"type":"string","description":"One attribute filter, as `key=value` (e.g. `agent.id=UXDT1W9fI3sj`). An operation may accept\n more forms; its `filters` member (query `filter`) says which."},"description":"Trace filters, one per entry; a trace matches when every entry holds.\n\n - `key=value`: the Trace.attributes value equals `value`, e.g. `filter=channel%3Dvoice`.\n - `key=*`: the attribute is present with a non-empty value, e.g. `filter=call.transferTo%3D*`.\n - `metric>=N` / `metric<=N`: an inclusive bound on a Trace.metrics value, e.g.\n `filter=durationMs%3E%3D60000`; both bounds on one metric give a window. A trace that\n carries no value for the metric matches no bound, so `usage.interruptions<=0` does not\n find a trace that never recorded it. `durationMs` is the trace's own wall-clock length:\n every ended trace has it, a pending one never does.\n\n `*` always means presence, so a literal `*` value cannot be matched; `key=` matches an\n empty value; when one key is given twice, the last entry wins. `N` is a plain decimal\n (`-12`, `0.5`) within ±9007199254740991 (2^53−1).\n\n Any other entry is a 400 ValidationException that names it: an empty entry, one with no\n `=`, one with nothing before `=` (`=x`), a reversed bound (`durationMs=>N`), strict `>` or\n `<`, a bound on a name that is not a trace metric, `key=value` on a metric name (bound it\n with `>=`/`<=` instead), and an `N` that is not a plain decimal\n (`0x10`, `1e3`) or lies outside that range."},"description":"Trace filters, one per entry; a trace matches when every entry holds.\n\n - `key=value`: the Trace.attributes value equals `value`, e.g. `filter=channel%3Dvoice`.\n - `key=*`: the attribute is present with a non-empty value, e.g. `filter=call.transferTo%3D*`.\n - `metric>=N` / `metric<=N`: an inclusive bound on a Trace.metrics value, e.g.\n `filter=durationMs%3E%3D60000`; both bounds on one metric give a window. A trace that\n carries no value for the metric matches no bound, so `usage.interruptions<=0` does not\n find a trace that never recorded it. `durationMs` is the trace's own wall-clock length:\n every ended trace has it, a pending one never does.\n\n `*` always means presence, so a literal `*` value cannot be matched; `key=` matches an\n empty value; when one key is given twice, the last entry wins. `N` is a plain decimal\n (`-12`, `0.5`) within ±9007199254740991 (2^53−1).\n\n Any other entry is a 400 ValidationException that names it: an empty entry, one with no\n `=`, one with nothing before `=` (`=x`), a reversed bound (`durationMs=>N`), strict `>` or\n `<`, a bound on a name that is not a trace metric, `key=value` on a metric name (bound it\n with `>=`/`<=` instead), and an `N` that is not a plain decimal\n (`0x10`, `1e3`) or lies outside that range."}]}},"/v2/traces/items/{traceId}":{"get":{"operationId":"GetTrace","summary":"","description":"Returns one trace and all spans recorded so far, ordered by start time.\n\n### Allowed Auth Scopes\n\n- wilma-traces:GetTrace\n- wilma-traces:read","parameters":[{"name":"traceId","in":"path","required":true,"schema":{"type":"string","maxLength":128,"minLength":1,"description":"Opaque trace identifier generated by the tracing runtime."},"description":"Opaque trace identifier generated by the tracing runtime."},{"name":"company","in":"query","required":false,"schema":{"type":"string","maxLength":30,"minLength":3}}]}},"/v2/traces/items/{traceId}/model-calls/{spanId}":{"get":{"operationId":"GetModelCall","summary":"","description":"Returns the exact request one model call of a session trace was sent, and what the model\n answered: the system prompt, every message, the tools offered with their definitions, the\n tool choice and the settings. Read from the conversation history the session archived, so\n nothing is clamped; files are described, never returned.\n\n NotFound names the reason in its message with one of these phrases, which clients may match:\n - `own model calls`: the span is not one of the session's own model calls.\n - `before model requests were recorded`: the call predates request recording.\n - `no longer archived`: the archived history it was sent is no longer kept.\n\n### Allowed Auth Scopes\n\n- wilma-traces:GetModelCall\n- wilma-traces:read","parameters":[{"name":"traceId","in":"path","required":true,"schema":{"type":"string","maxLength":128,"minLength":1,"description":"Opaque trace identifier generated by the tracing runtime."},"description":"Opaque trace identifier generated by the tracing runtime."},{"name":"spanId","in":"path","required":true,"schema":{"type":"string","maxLength":128,"minLength":1,"description":"The `llm.call` span's id, as `GetTrace` returns it."},"description":"The `llm.call` span's id, as `GetTrace` returns it."},{"name":"company","in":"query","required":false,"schema":{"type":"string","maxLength":30,"minLength":3}}]}},"/v2/traces/items/{traceId}/stream":{"get":{"operationId":"StreamTrace","summary":"","description":"Replays the current full snapshot of one trace and then tails it as\n Server-Sent Events. traceUpserted and spanUpserted are full snapshots. The\n stream remains usable after status=complete because new work may reopen the\n trace.\n\n### Allowed Auth Scopes\n\n- wilma-traces:StreamTrace\n- wilma-traces:read","parameters":[{"name":"traceId","in":"path","required":true,"schema":{"type":"string","maxLength":128,"minLength":1,"description":"Opaque trace identifier generated by the tracing runtime."},"description":"Opaque trace identifier generated by the tracing runtime."},{"name":"company","in":"query","required":false,"schema":{"type":"string","maxLength":30,"minLength":3}}]}},"/v2/traces/metrics":{"get":{"operationId":"QueryMetrics","summary":"","description":"Aggregates span-level metrics from traces. QueryUsage answers session-level questions\n (how many sessions, what AI cost); QueryMetrics answers per-entity funnels inside them:\n filter spans by name and attribute equality, bucket by time, group by span attribute keys\n and sum the spans' numeric attributes plus a `spans` count of matching spans.\n\n Examples: copilot per-resource funnels (spans asset.shown / asset.seen / action.clicked\n grouped by asset.id — emitted vs seen vs clicked), per-tool usage of agents, per-skill\n usage of assistants.\n\n### Allowed Auth Scopes\n\n- wilma-traces:QueryMetrics\n- wilma-traces:read","parameters":[{"name":"company","in":"query","required":false,"schema":{"type":"string","maxLength":30,"minLength":3}},{"name":"service","in":"query","required":false,"schema":{"type":"string"}},{"name":"name","in":"query","required":false,"schema":{"type":"array","items":{"type":"string"},"description":"Span names to aggregate, e.g. asset.shown. Multiple names aggregate together; group by `name` to split them."},"description":"Span names to aggregate, e.g. asset.shown. Multiple names aggregate together; group by `name` to split them."},{"name":"from","in":"query","required":false,"schema":{"type":"string","description":"Inclusive start of the window on the span's startTime: an ISO-8601 instant with its offset\n (`2026-07-10T14:12:30.123Z`, `2026-07-10T16:12:30+02:00`) or a date `YYYY-MM-DD`, read as\n midnight UTC. Defaults to 30 days before `to`. Any other spelling, or a `from` later than\n `to`, is a 400."},"description":"Inclusive start of the window on the span's startTime: an ISO-8601 instant with its offset\n (`2026-07-10T14:12:30.123Z`, `2026-07-10T16:12:30+02:00`) or a date `YYYY-MM-DD`, read as\n midnight UTC. Defaults to 30 days before `to`. Any other spelling, or a `from` later than\n `to`, is a 400."},{"name":"to","in":"query","required":false,"schema":{"type":"string","description":"Exclusive end of the window, in the spelling `from` takes: the window is `[from, to)`.\n Defaults to now."},"description":"Exclusive end of the window, in the spelling `from` takes: the window is `[from, to)`.\n Defaults to now."},{"name":"interval","in":"query","required":false,"schema":{"type":"string","enum":["hour","day","week","month","total"]},"description":"Time bucket size. Omit for one row per group over the whole range."},{"name":"groupBy","in":"query","required":false,"schema":{"type":"array","items":{"type":"string"},"description":"Span attribute keys to group by (e.g. `asset.id`, `automation.id`), plus the reserved key `name` for the span name. A key that is neither `name` nor a declared trace attribute is a 400."},"description":"Span attribute keys to group by (e.g. `asset.id`, `automation.id`), plus the reserved key `name` for the span name. A key that is neither `name` nor a declared trace attribute is a 400."},{"name":"limit","in":"query","required":false,"schema":{"type":"number","maximum":100,"minimum":1}},{"name":"cursor","in":"query","required":false,"schema":{"type":"string","maxLength":2048,"minLength":1,"description":"Opaque cursor returned by a previous page; pass it back with the same `groupBy`. A cursor\n from another grouping, or one altered, is a 400."},"description":"Opaque cursor returned by a previous page; pass it back with the same `groupBy`. A cursor\n from another grouping, or one altered, is a 400."},{"name":"filter","in":"query","required":false,"schema":{"type":"array","items":{"type":"string","description":"One attribute filter, as `key=value` (e.g. `agent.id=UXDT1W9fI3sj`). An operation may accept\n more forms; its `filters` member (query `filter`) says which."},"description":"Span filters, one per entry, in the QueryTraces grammar. `key=value` matches a span\n attribute exactly, e.g. `filter=asset.id%3Dasset-1&filter=channel%3Dcalls`; a span inherits\n its trace's attributes, so a trace-level key narrows spans too. `key=*` keeps spans where\n the attribute is present. `metric>=N` / `metric<=N` bound the span's own metrics, not its\n trace's: a span carries `durationMs` and its `usage.*` counters only, so a bound on any\n other metric (`spans`, `spans.errors`, `latency.*`) is a 400. An entry outside the\n grammar is a 400 ValidationException naming it, as QueryTraces describes."},"description":"Span filters, one per entry, in the QueryTraces grammar. `key=value` matches a span\n attribute exactly, e.g. `filter=asset.id%3Dasset-1&filter=channel%3Dcalls`; a span inherits\n its trace's attributes, so a trace-level key narrows spans too. `key=*` keeps spans where\n the attribute is present. `metric>=N` / `metric<=N` bound the span's own metrics, not its\n trace's: a span carries `durationMs` and its `usage.*` counters only, so a bound on any\n other metric (`spans`, `spans.errors`, `latency.*`) is a 400. An entry outside the\n grammar is a 400 ValidationException naming it, as QueryTraces describes."}]}},"/v2/traces/stream":{"get":{"operationId":"StreamService","summary":"","description":"Streams trace summary upserts across the service. Filter by required Trace\n service and indexed attributes. A complete trace can appear again with a\n newer revision after new spans are appended.\n\n### Allowed Auth Scopes\n\n- wilma-traces:StreamService\n- wilma-traces:read","parameters":[{"name":"company","in":"query","required":false,"schema":{"type":"string","maxLength":30,"minLength":3}},{"name":"service","in":"query","required":false,"schema":{"type":"string"}},{"name":"filter","in":"query","required":false,"schema":{"type":"array","items":{"type":"string","description":"One attribute filter, as `key=value` (e.g. `agent.id=UXDT1W9fI3sj`). An operation may accept\n more forms; its `filters` member (query `filter`) says which."},"description":"Trace attribute equality filters, one `key=value` per entry. The presence and range forms\n QueryTraces accepts are a 400 here."},"description":"Trace attribute equality filters, one `key=value` per entry. The presence and range forms\n QueryTraces accepts are a 400 here."}]}},"/v2/traces/usage":{"get":{"operationId":"QueryUsage","summary":"","description":"Aggregates usage from traces. For conversational services one trace is\n one logical session, so 'sessions' is the distinct trace count and\n 'usage.cost' is summed once per trace. Results can be bucketed by time and\n grouped by indexed Trace.attributes such as agent.id, channel and\n user.id.\n\n### Allowed Auth Scopes\n\n- wilma-traces:QueryUsage\n- wilma-traces:read","parameters":[{"name":"company","in":"query","required":false,"schema":{"type":"string","maxLength":30,"minLength":3}},{"name":"service","in":"query","required":false,"schema":{"type":"string"}},{"name":"from","in":"query","required":false,"schema":{"type":"string","description":"Inclusive start of the window on the trace's startTime: an ISO-8601 instant with its offset\n (`2026-07-10T14:12:30.123Z`, `2026-07-10T16:12:30+02:00`) or a date `YYYY-MM-DD`, read as\n midnight UTC. Defaults to 30 days before `to`. Any other spelling, or a `from` later than\n `to`, is a 400."},"description":"Inclusive start of the window on the trace's startTime: an ISO-8601 instant with its offset\n (`2026-07-10T14:12:30.123Z`, `2026-07-10T16:12:30+02:00`) or a date `YYYY-MM-DD`, read as\n midnight UTC. Defaults to 30 days before `to`. Any other spelling, or a `from` later than\n `to`, is a 400."},{"name":"to","in":"query","required":false,"schema":{"type":"string","description":"Exclusive end of the window, in the spelling `from` takes: the window is `[from, to)`.\n Defaults to now."},"description":"Exclusive end of the window, in the spelling `from` takes: the window is `[from, to)`.\n Defaults to now."},{"name":"interval","in":"query","required":false,"schema":{"type":"string","enum":["hour","day","week","month","total"]}},{"name":"groupBy","in":"query","required":false,"schema":{"type":"array","items":{"type":"string"},"description":"Trace attribute keys to group by, e.g. `agent.id`, `channel`. A key that is not a declared trace attribute is a 400."},"description":"Trace attribute keys to group by, e.g. `agent.id`, `channel`. A key that is not a declared trace attribute is a 400."},{"name":"limit","in":"query","required":false,"schema":{"type":"number","maximum":100,"minimum":1}},{"name":"cursor","in":"query","required":false,"schema":{"type":"string","maxLength":2048,"minLength":1,"description":"Opaque cursor returned by a previous page; pass it back with the same `groupBy`. A cursor\n from another grouping, or one altered, is a 400."},"description":"Opaque cursor returned by a previous page; pass it back with the same `groupBy`. A cursor\n from another grouping, or one altered, is a 400."},{"name":"filter","in":"query","required":false,"schema":{"type":"array","items":{"type":"string","description":"One attribute filter, as `key=value` (e.g. `agent.id=UXDT1W9fI3sj`). An operation may accept\n more forms; its `filters` member (query `filter`) says which."},"description":"Trace filters, one per entry, in the QueryTraces grammar: `key=value` for an exact\n Trace.attributes value (e.g. filter=agent.id%3Dsupport-agent&filter=channel%3Dvoice),\n `key=*` for a present attribute, `metric>=N` / `metric<=N` for an inclusive bound on a\n Trace.metrics value. Only the traces that match are summed. An entry outside that grammar\n is a 400 ValidationException naming it, as QueryTraces describes."},"description":"Trace filters, one per entry, in the QueryTraces grammar: `key=value` for an exact\n Trace.attributes value (e.g. filter=agent.id%3Dsupport-agent&filter=channel%3Dvoice),\n `key=*` for a present attribute, `metric>=N` / `metric<=N` for an inclusive bound on a\n Trace.metrics value. Only the traces that match are summed. An entry outside that grammar\n is a 400 ValidationException naming it, as QueryTraces describes."}]}},"/v2/voice/calls/{callId}/events":{"get":{"operationId":"StreamCallEvents","summary":"","description":"Tails a live call's events as Server-Sent Events (`text/event-stream`): the caller's recognised speech, the agent's spoken sentences, playback, interruptions, language switches and the session lifecycle. Live only — nothing before the subscription is replayed, and the stream is not a record of the call (the session trace is). Ends after an `ended` event whose reason is not `transfer`; after a transfer it stays open briefly for the next session of the same call. What the stream carries is decided HERE, by who is asking, and is not a request parameter: a caller signed in to the system receives everything, including the agent's own commands (`reply`, `say`, `playback`, `interruption`) and its tool calls, and chooses for itself what to show; a guest receives the transcript alone (`started`, `transcription`, `agentTranscription`, `language`, `ended`).\n\n### Allowed Auth Scopes\n\n- wilma-voice:StreamCallEvents\n- wilma-voice:read","parameters":[{"name":"callId","in":"path","required":true,"schema":{"type":"string","description":"The PBX call id (LinkedID) — `callId` of the voice session's connect query, and the SIP `X-Session-ID` header the caller's own leg received."},"description":"The PBX call id (LinkedID) — `callId` of the voice session's connect query, and the SIP `X-Session-ID` header the caller's own leg received."},{"name":"company","in":"query","required":false,"schema":{"type":"string","maxLength":30,"minLength":3,"description":"Owning company. Only S2S callers may set it; every other token carries its company."},"description":"Owning company. Only S2S callers may set it; every other token carries its company."}]}},"/v2/voice/describe":{"post":{"operationId":"DescribeVoiceProtocol","summary":"","description":"Describes the WebSocket connect parameters, events and commands of the voice protocol. Utility method; the generated shapes are the source of truth for building a WebSocket client or server by hand.\n\n### Allowed Auth Scopes\n\n- wilma-voice:DescribeVoiceProtocol\n- wilma-voice:read","parameters":[]}},"/v2/voice/sessions/{sessionId}/hangup":{"post":{"operationId":"SendHangup","summary":"","description":"Terminates the voice session and the call.\n\n### Allowed Auth Scopes\n\n- wilma-voice:SendHangup","parameters":[{"name":"sessionId","in":"path","required":true,"schema":{"type":"string","maxLength":128,"minLength":1,"description":"Caller-facing voice session identifier, unique per connection. Supplied by the client at connect (the PBX call/session id, or a client-generated id for web sessions)."},"description":"Caller-facing voice session identifier, unique per connection. Supplied by the client at connect (the PBX call/session id, or a client-generated id for web sessions)."}],"requestBody":{"required":false,"schema":{"type":"object","description":"Terminate the session and hang up the call.","properties":{"reason":{"type":"string"}}}}}},"/v2/voice/sessions/{sessionId}/play":{"post":{"operationId":"SendPlay","summary":"","description":"Plays the specified audio chunk through the audio stream of the voice session.\n\n### Allowed Auth Scopes\n\n- wilma-voice:SendPlay","parameters":[{"name":"sessionId","in":"path","required":true,"schema":{"type":"string","maxLength":128,"minLength":1,"description":"Caller-facing voice session identifier, unique per connection. Supplied by the client at connect (the PBX call/session id, or a client-generated id for web sessions)."},"description":"Caller-facing voice session identifier, unique per connection. Supplied by the client at connect (the PBX call/session id, or a client-generated id for web sessions)."}],"requestBody":{"required":true,"schema":{"type":"object","description":"A chunk of synthesized reply audio to play. `data` is base64, PCM-encoded at the sample rate declared in the matching `say` command; an empty `data` marks the end of the stream.","properties":{"replyId":{"type":"string","description":"Correlates a caller phrase, the agent reply it produced, and the playback/interruption of that reply."},"streamId":{"type":"string","description":"Identifies one synthesized audio stream within a session; carried by a `say` command and the `play` frames that render it."},"data":{"type":"string"}},"required":["data","replyId","streamId"]}}}},"/v2/voice/sessions/{sessionId}/say":{"post":{"operationId":"SendSay","summary":"","description":"Generates and plays the specified text through the audio stream of the voice session.\n\n### Allowed Auth Scopes\n\n- wilma-voice:SendSay","parameters":[{"name":"sessionId","in":"path","required":true,"schema":{"type":"string","maxLength":128,"minLength":1,"description":"Caller-facing voice session identifier, unique per connection. Supplied by the client at connect (the PBX call/session id, or a client-generated id for web sessions)."},"description":"Caller-facing voice session identifier, unique per connection. Supplied by the client at connect (the PBX call/session id, or a client-generated id for web sessions)."}],"requestBody":{"required":true,"schema":{"type":"object","description":"Play a reply to the caller. When `audio` is absent the client performs text-to-speech from `text`; when present the server streams synthesized `play` frames for this reply.","properties":{"text":{"type":"string"},"replyId":{"type":"string","description":"Correlates a caller phrase, the agent reply it produced, and the playback/interruption of that reply."},"interruptible":{"type":"boolean","description":"Whether the caller may barge in over this reply."},"voice":{"type":"string","maxLength":64,"minLength":1,"pattern":"^[A-Za-z0-9]+$","description":"Bare Amazon Polly voice id, e.g. 'Joanna' — NOT a URI. The PBX consumes this verbatim as the\n AMI `Voice=` action parameter, so it takes exactly what Polly names the voice, with the\n `amazon_polly://` scheme and any query parameters already stripped by whoever builds this\n command out of the agent's stored `VoiceUri`.\n\n Set only for a provider whose synthesis runs ON THE PBX (Amazon Polly); ElevenLabs and Google\n are synthesized in the cloud and streamed as PCM through `play` instead, and leave this\n absent."},"audio":{"type":"object","description":"Audio-stream parameters when the server synthesizes the reply itself (TTS provider configured on the agent): the `say` is followed by a stream of `play` frames on this stream id.","properties":{"streamId":{"type":"string","description":"Identifies one synthesized audio stream within a session; carried by a `say` command and the `play` frames that render it."},"sampleRate":{"type":"number"}},"required":["sampleRate","streamId"]}},"required":["replyId","text"]}}}},"/v2/voice/sessions/{sessionId}/stop":{"post":{"operationId":"SendStop","summary":"","description":"Stops any audio currently being played by the voice session.\n\n### Allowed Auth Scopes\n\n- wilma-voice:SendStop","parameters":[{"name":"sessionId","in":"path","required":true,"schema":{"type":"string","maxLength":128,"minLength":1,"description":"Caller-facing voice session identifier, unique per connection. Supplied by the client at connect (the PBX call/session id, or a client-generated id for web sessions)."},"description":"Caller-facing voice session identifier, unique per connection. Supplied by the client at connect (the PBX call/session id, or a client-generated id for web sessions)."}]}},"/v2/voice/sessions/{sessionId}/transfer":{"post":{"operationId":"SendTransfer","summary":"","description":"Transfers the call to the specified context and extension, and terminates the voice session.\n\n### Allowed Auth Scopes\n\n- wilma-voice:SendTransfer","parameters":[{"name":"sessionId","in":"path","required":true,"schema":{"type":"string","maxLength":128,"minLength":1,"description":"Caller-facing voice session identifier, unique per connection. Supplied by the client at connect (the PBX call/session id, or a client-generated id for web sessions)."},"description":"Caller-facing voice session identifier, unique per connection. Supplied by the client at connect (the PBX call/session id, or a client-generated id for web sessions)."}],"requestBody":{"required":true,"schema":{"type":"object","description":"Transfer the call to a PBX destination and terminate the session. Honoured on PBX clients.","properties":{"context":{"type":"string"},"extension":{"type":"string"}},"required":["context","extension"]}}}},"/v2/voice/text-to-speech":{"post":{"operationId":"TextToSpeech","summary":"","description":"Synthesizes one phrase and returns MP3.\n\n NOT NAMED FOR THE PREVIEW IT SERVES. Auditioning an unsaved voice in the console is the first\n caller, but nothing about the operation is preview-specific — it is the platform's\n text-to-speech, reachable by anything holding the scope, and a name naming one caller's screen\n would have to be worked around by the second one.\n\n EVERYTHING TRAVELS IN THE REQUEST and the operation reads no stored record — not the agent, and\n no dictionary behind it. The settings being auditioned are unsaved form state, and the rules\n that apply are the ones on `pronunciations`, so what is heard here is what the call would say.\n\n### Allowed Auth Scopes\n\n- wilma-voice:TextToSpeech","parameters":[],"requestBody":{"required":true,"schema":{"type":"object","properties":{"voice":{"type":"string","maxLength":1024,"minLength":1,"pattern":"^[a-z0-9][a-z0-9_-]*://[a-zA-Z0-9._~/-]+(\\?[a-zA-Z0-9._~%&=+:/-]*)?$","description":"Voice to speak with, as the URI a call would use — provider, voice id, synthesis parameters and, for a company's own account, `apiKey=secrets:{secretId}`. Passing the call's own value is what keeps an audition from diverging from the call it is auditioning."},"language":{"type":"string","maxLength":8,"minLength":2,"pattern":"^[a-z]{2,3}(-[A-Z]{2})?$","description":"Language the phrase is in. Reaches the provider as the synthesis language, and is the language `pronunciations` was written for."},"text":{"type":"string","maxLength":500,"minLength":1,"description":"Text to speak, BEFORE pronunciation rules are applied."},"pronunciations":{"type":"array","items":{"type":"object","description":"A pronunciation rule for one phrase. Belongs to a LANGUAGE, not to the agent: 'Wildix' is\n respelled differently for an Italian and a German voice, so one list shared ACROSS languages\n would fight itself. That is why the rules hang off the language and nowhere else — there is no\n company-wide dictionary above them to inherit from, or to disagree with.","properties":{"phrase":{"type":"string","maxLength":200,"description":"The phrase as it appears in the text, e.g. 'Wildix'. Bounded because every rule is compiled into ONE regular expression — see the list below."},"as":{"type":"string","maxLength":200,"description":"Respelling (ALIAS) or phonetic transcription (IPA), e.g. 'Wil-dix'."},"encoding":{"type":"string","description":"How the `as` member of a pronunciation rule is written.","enum":["alias","ipa"]}},"required":["as","phrase"]},"maxItems":200,"description":"Rules to apply to `text`, applied SERVER-SIDE by the same helper the call path uses — never pre-applied by the caller, or the form reimplements a rule the server owns and can silently diverge from the call. Pass the language's own list to hear exactly what a call would say."}},"required":["language","text","voice"]}}}},"/v2/voice/voices":{"get":{"operationId":"ListVoices","summary":"","description":"Lists the voices a synthesis provider offers, read from the provider's own API and cached per company, provider, credential and language. Answers a URI per voice, so a picker stores what it selected rather than composing one.\n\n### Allowed Auth Scopes\n\n- wilma-voice:ListVoices\n- wilma-voice:read","parameters":[{"name":"provider","in":"query","required":true,"schema":{"type":"string","maxLength":32,"minLength":1,"pattern":"^[a-z0-9][a-z0-9_-]*$","description":"Provider whose catalog to read: 'elevenlabs', 'google', 'amazon_polly', 'soniox'. This is the scheme of the URIs answered on `voices`."},"description":"Provider whose catalog to read: 'elevenlabs', 'google', 'amazon_polly', 'soniox'. This is the scheme of the URIs answered on `voices`."},{"name":"language","in":"query","required":false,"schema":{"type":"string","maxLength":8,"minLength":2,"pattern":"^[a-z]{2,3}(-[A-Z]{2})?$","description":"Server-side filter for language-bound providers: only voices whose language SET CONTAINS this code. Ignored for ElevenLabs, whose voices are not language bound. For Soniox it keeps the voices whose model speaks the language."},"description":"Server-side filter for language-bound providers: only voices whose language SET CONTAINS this code. Ignored for ElevenLabs, whose voices are not language bound. For Soniox it keeps the voices whose model speaks the language."},{"name":"credential","in":"query","required":false,"schema":{"type":"string","maxLength":72,"minLength":9,"pattern":"^secrets:[a-zA-Z0-9_-]{1,64}$","description":"A reference to the company's own provider credential, so the picker lists that account's\n voices including cloned ones. Absent means the platform key.\n\n `secrets:{secretId}` ONLY, resolved under the CALLER's company — an id that does not resolve\n there is rejected. There is no form of this member that carries a key, which is what keeps a\n provider credential out of a URL on this stack: every one of them lives in `wilma-secrets`,\n and a reference in a query string is not a secret.\n\n A stored voice URI says the same thing the same way: its `apiKey` parameter is a reference,\n and `validateSpeech` refuses a literal key in one."},"description":"A reference to the company's own provider credential, so the picker lists that account's\n voices including cloned ones. Absent means the platform key.\n\n `secrets:{secretId}` ONLY, resolved under the CALLER's company — an id that does not resolve\n there is rejected. There is no form of this member that carries a key, which is what keeps a\n provider credential out of a URL on this stack: every one of them lives in `wilma-secrets`,\n and a reference in a query string is not a secret.\n\n A stored voice URI says the same thing the same way: its `apiKey` parameter is a reference,\n and `validateSpeech` refuses a literal key in one."}]}}}}