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

modeHowCost and latency
template (default)A JSON template with variables — the state, the answers, weighted levels, composites and params.Free, instant: no model call.
extractAn 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"]
}
KeywordUse
typestring, number, integer, boolean, object or array — or ["string", "null"] to accept null. The root is always an object.
properties, requiredThe fields of an object, and which ones must be present. Every object declares its fields.
itemsWhat a list holds (required on array).
enumAllowed values of a text or number field (up to 50).
descriptionWhat a field means — the model reads it in extract mode.
minimum, maximum, minLength, maxLength, minItems, maxItemsBounds 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
}
FieldDefaultDescription
provideropenrouteropenrouter 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.
inputthe whole stateThe user message.
temperature00 to 2.
maxTokens102416 to 4,096.
timeoutMs200001,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:

Caseoutput
A json destination completedIts object — the first one, in schema order, when several fired.
json destinations fired, none completednull
No json destination firedabsent

In the Playground and the logs

  • In the Playground, template destinations always run (they're free); extract destinations show the prompt and the strict schema that would be sent ("status": "preview") unless you run destinations for real.
  • The execution log keeps the output when the decision stores outputs (storeOutput); with storeOutput: false it keeps only whether the output was built.
  • The SDKs expose it as decision.output — in TypeScript, typed with dcision.decide<Result, Output>(…).

On this page