Structured output
Return a JSON object your system can use as is — built from the result with a template (no model) or extracted from the state by an LLM, always checked against the fields you define.
A decision answers questions: result, confidence and the action. A structured output destination (json) also hands back an object ready for your system — the ticket to open, the order to update, the lead to create — and checks it against the fields you define before it leaves Dcision.
It comes back in the response's top-level output:
{
"result": { "route": "support", "priority": "high" },
"action": "continue",
"output": { "awb": "1Z999AA10123456784", "team": "support", "priority": 3, "summary": "Package is 3 days late" },
"destinations": [
{ "key": "ticket", "type": "json", "mode": "template", "status": "completed", "output": { "awb": "1Z999AA10123456784", "team": "support", "priority": 3, "summary": "Package is 3 days late" } }
]
}Two ways to fill it
mode | How | Cost and latency |
|---|---|---|
template (default) | A JSON template with variables — the state, the answers, weighted levels, composites and params. | Free, instant: no model call. |
extract | An LLM reads the state and fills the fields, on your OpenRouter or Vercel AI Gateway key — like LLM answers. | The provider's tokens; the caller waits (it counts as one of the 3 calls that make the caller wait). |
Use template when the values already exist — in the state or in the answers. Use extract when they are buried in text: an order number in an e-mail, a name in a chat message.
The fields
The schema is a JSON Schema object, the same format LLM providers use for structured outputs:
{
"type": "object",
"properties": {
"awb": { "type": "string", "description": "The tracking number" },
"team": { "type": "string", "enum": ["sales", "support", "billing"] },
"priority": { "type": "integer", "minimum": 1, "maximum": 4 },
"items": { "type": "array", "items": { "type": "string" }, "maxItems": 10 },
"note": { "type": ["string", "null"] }
},
"required": ["awb", "team"]
}| Keyword | Use |
|---|---|
type | string, number, integer, boolean, object or array — or ["string", "null"] to accept null. The root is always an object. |
properties, required | The fields of an object, and which ones must be present. Every object declares its fields. |
items | What a list holds (required on array). |
enum | Allowed values of a text or number field (up to 50). |
description | What a field means — the model reads it in extract mode. |
minimum, maximum, minLength, maxLength, minItems, maxItems | Bounds of numbers, texts and lists. |
Fields that aren't in the schema are not accepted. Limits: up to 3 levels of nested objects, 50 fields in total and 8 KB of schema; the output itself is up to 16 KB. In the editor, Output fields builds the schema row by row; the JSON Schema tab shows it as JSON, and you can paste one there.
Template mode
The template is JSON with variables, encoded like the body of an API request: outside quotes a variable becomes a JSON value, inside quotes it becomes text.
{
"key": "ticket",
"type": "json",
"when": { "conditions": [{ "field": "route", "on": "output", "operator": "eq", "value": "support" }] },
"params": { "awb": { "from": "state.awb", "required": true } },
"json": {
"mode": "template",
"schema": { "type": "object", "properties": { "awb": { "type": "string" }, "team": { "type": "string" }, "priority": { "type": "number" }, "summary": { "type": "string" } }, "required": ["awb", "team"] },
"template": "{\"awb\": {{params.awb}}, \"team\": {{result.route}}, \"priority\": {{scores.priority}}, \"summary\": \"Customer said: {{state.message}}\"}"
}
}- Text from the state can't break the JSON: quotes and line breaks are escaped wherever the variable is.
- Secrets can't be used — the output goes back to the caller.
{{secrets.NAME}}is refused when you save. - When you save, the template must be valid JSON with sample values and have the required fields. Fill from fields in the editor writes a starting template: a field named like a state field, a question, a composite or a param reads it.
Extract mode
"json": {
"mode": "extract",
"schema": { "type": "object", "properties": { "order_id": { "type": "string" }, "customer": { "type": "string" }, "request": { "type": "string", "enum": ["refund", "exchange", "tracking"] } }, "required": ["order_id", "request"] },
"provider": "openrouter",
"model": "openai/gpt-5-mini",
"instructions": "Extract the order this customer is writing about.",
"input": "{{state.message}}",
"temperature": 0,
"maxTokens": 1024,
"timeoutMs": 20000
}| Field | Default | Description |
|---|---|---|
provider | openrouter | openrouter or vercel — the key saved in Settings → Engine. |
model | — | The provider's model ID. Pick one that supports structured outputs (JSON Schema). |
instructions | — | What to extract (system message). Variables allowed, no secrets. |
input | the whole state | The user message. |
temperature | 0 | 0 to 2. |
maxTokens | 1024 | 16 to 4,096. |
timeoutMs | 20000 | 1,000 to 25,000, retries included. |
Dcision sends the schema in the provider's strict structured-output mode (response_format: json_schema). Strict mode needs every field, so optional fields go as nullable and the nulls the model writes for them are removed before validation. If the answer still doesn't fit — invalid JSON, a value outside enum, a missing field — Dcision sends it back once with the problems, within the same timeoutMs.
When it fails
A structured output never fails the decision. The entry comes back with "status": "failed", an error and, when the object didn't match, the issues:
{
"key": "ticket",
"type": "json",
"mode": "template",
"status": "failed",
"error": "The output doesn't match the schema.",
"issues": [{ "path": "/awb", "message": "Must not be null." }]
}issues[].path is a JSON Pointer to the value (/items/2). The top-level output is:
| Case | output |
|---|---|
A json destination completed | Its object — the first one, in schema order, when several fired. |
json destinations fired, none completed | null |
No json destination fired | absent |
In the Playground and the logs
- In the Playground,
templatedestinations always run (they're free);extractdestinations show the prompt and the strict schema that would be sent ("status": "preview") unless you run destinations for real. - The execution log keeps the
outputwhen the decision stores outputs (storeOutput); withstoreOutput: falseit keeps only whether the output was built. - The SDKs expose it as
decision.output— in TypeScript, typed withdcision.decide<Result, Output>(…).
Fixed replies
Return a ready-made message — text and optional buttons, with variables — in the API response for a given result, instantly and without calling any model.
LLM answers
Let a model answer for a route — Dcision calls OpenRouter or Vercel AI Gateway with your workspace's own key, the route's prompt as the system message and the state as input.