Model Output JSON Guard
ai.output_json_guard
Overview
You asked a model for JSON and got back a fenced code block wrapped in an apology, or an object with a sentence after it, or something that is nearly JSON but has a trailing comma in it. Send us the raw reply and we find the JSON, parse it strictly, and hand back the parsed value along with where in your text it came from and which extraction rung found it. Parsing is RFC 8259 and nothing looser: no comments, no trailing commas, no single quotes, no unquoted keys, no NaN or Infinity. When something fails we name the exact construct and where it sits, so you know whether to retry the model or send the text to schema.repair_json. A reply with no JSON in it is not an error here — it comes back as valid false, because that is the answer you asked for. We check that it is JSON; we do not check it against a schema, and schema.validate_json is the one skill on the platform that does.
Every team that asks a model for JSON writes the same fragile snippet: strip the fences, find the first brace, hope. It works until a value contains a closing brace, or the model sends two blocks, or the reply is cut off mid-object — and then it fails quietly with the wrong half of a payload. Doing it once, string-aware, with the rung reported and every malformation named, turns that snippet into something you can reason about.
Reliability
Fully deterministic: the same reply and settings always give the same verdict, with no clock, randomness or network involved, and the parse is done by a mature streaming parser rather than hand-rolled. A reply with no JSON is a valid false answer, not a failure. The failures, which are all about the request or a hard ceiling: GUARD_TEXT_INVALID means text was empty, so there is nothing to look in — send the reply and retry; GUARD_TEXT_TOO_LARGE means the reply passed 2000000 characters, which is one reply's worth rather than a whole transcript, and retrying unchanged will fail again; GUARD_EXTRACTION_UNSUPPORTED means extraction, expect or onMultipleCandidates named a value outside its list, and we refuse rather than fall back to a default you did not choose; GUARD_MULTIPLE_CANDIDATES means more than one JSON value was found while onMultipleCandidates is REJECT — switch to FIRST or LARGEST, or narrow extraction; GUARD_DEPTH_EXCEEDED means the payload nests deeper than maxDepth, or maxDepth itself was outside 1 to 64 — raise it up to 64, and past that the payload is genuinely beyond what we accept; GUARD_CANDIDATE_LIMIT_EXCEEDED means one rung found more candidates than maxCandidates, or maxCandidates was outside 1 to 100; GUARD_OUTPUT_TOO_LARGE means the JSON found is over 1,048,576 characters and returnJson is on — send returnJson false and you still get the verdict, the span and the diagnostics. None of these are retryable unchanged.
Context footprint
Accepts up to 2000000 characters of model output. The answer carries the parsed value, which is bounded by the extracted span and capped at 1,048,576 characters unless you turn returnJson off, plus at most 20 diagnostics. Nothing else from your text comes back: the error messages carry line, column and member path, never the characters at that position. A successful call costs 1 execution credit.
When to use it
Immediately after any model call whose reply is supposed to be JSON, before anything downstream touches it.
When the question is whether the JSON matches your schema, when you want the broken JSON fixed rather than diagnosed, or when the payload is already known-clean JSON from a machine.
Powered by Jackson's streaming parser, the JVM's most widely deployed JSON engine, so strictness comes from a battle-tested implementation of RFC 8259.
Scope
Non-goals
- Does not validate against a JSON Schema — schema.validate_json is the platform's only validator
- Does not repair malformed JSON — schema.repair_json does that, and the error code here tells you when to go there
- Does not call a model or retry one for you
- Does not accept relaxed JSON: comments, trailing commas, single quotes, unquoted keys, NaN and Infinity are all refused by name
- Does not reformat or pretty-print; you get the parsed value, not new text
Properties
Capabilities
Cost & billing
Flat rate per invocation
File handling
How we handle your data
What this means
This skill may receive sensitive content and may return submitted, transformed, extracted, or reformatted content.
Squillify guarantees
- Runs inside Squillify
- This skill runs inside Squillify.
- Payloads are not sent to an external service by this skill.
- Squillify does not permanently store submitted payloads.
- Telemetry records metadata only, not submitted content.
- Submitted payloads are not used for training.
- Submitted payloads are not reviewed by humans.
- This skill does not target sensitive data.
- The response may contain submitted or transformed content.
Your responsibility
Squillify does not determine whether submitted content is sensitive. Your application is responsible for deciding what data it is allowed to submit. Your application is responsible for securing the response.
Input schema
| Field | Type | Required | Description |
|---|---|---|---|
| text | string | Yes | Raw model reply containing the JSON to locate and parse. The skill refuses more than 2,000,000 characters with GUARD_TEXT_TOO_LARGE. |
| extraction | string | No | Extraction ladder; omit it for AUTO. Published values: AUTO, FENCED_ONLY, WHOLE_TEXT_ONLY, FIRST_BALANCED. An unrecognised mode is GUARD_EXTRACTION_UNSUPPORTED. |
| expect | string | No | Required JSON shape; omit it for ANY. Published values: ANY, OBJECT, ARRAY. An unrecognised shape is GUARD_EXTRACTION_UNSUPPORTED. |
| onMultipleCandidates | string | No | Tie-break when one rung finds several values; omit it for FIRST. Published values: FIRST, LARGEST, REJECT. An unrecognised tie-break is GUARD_EXTRACTION_UNSUPPORTED. |
| allowTrailingText | boolean | No | Whether prose around the JSON is allowed; omit it for true. |
| maxDepth | integer | No | Nesting ceiling enforced while parsing; omit it for 64. |
| maxCandidates | integer | No | How many candidates one rung may find; omit it for 20. |
| returnJson | boolean | No | Whether the parsed value is returned in json; omit it for true. |
Output schema
| Field | Type | Required | Description |
|---|---|---|---|
| valid | boolean | Yes | Whether a JSON value of the demanded shape was found and parsed. |
| json | object | No | Parsed JSON value returned only when valid is true and returnJson stays on. |
| extractedBy | string | Yes | Ladder rung that produced the chosen value, or NONE. |
| candidateCount | integer | Yes | How many matching candidates the winning rung found. |
| span | object | Yes | Closed character-offset span into the supplied text; both offsets are 0 when nothing was found. |
| trailingTextPresent | boolean | Yes | Whether non-whitespace sits outside the chosen span. |
| errors | array | Yes | Bounded array of 0 through 20 closed findings with code, message, and optional placement. |
| summary | object | Yes | Closed extraction summary with input size, span size, depth, and expected shape. |
API contract
POST /v1/executecurl -sS -X POST "$BASE/v1/execute" -H "Authorization: Bearer $KEY" -H 'Content-Type: application/json' -d '{"skillKey":"ai.output_json_guard","input":{"text":"Here you go:\n```json\n{\"category\":\"billing\"}\n```","expect":"OBJECT"}}'import { Squillify } from "@squillify/sdk";
const sq = new Squillify({ apiKey: process.env.SQUILLIFY_API_KEY });
const { output } = await sq.execute("ai.output_json_guard", {"expect":"OBJECT","text":"Sure — here is the classification you asked for.\n\n```json\n{\"category\": \"billing\", \"confidence\": 0.82, \"tags\": [\"invoice\", \"refund\"]}\n```\n\nLet me know if you would like it broken down further."});Errors
| Code | HTTP | Retryable | Description |
|---|---|---|---|
| VALIDATION_ERROR | 400 | No | The request payload failed schema validation. |
| UNAUTHORIZED | 401 | No | Missing or invalid API key. |
| SKILL_NOT_ACCESSIBLE | 403 | No | The API key is not granted access to this skill. |
| SKILL_NOT_AVAILABLE_FOR_PLAN | 403 | No | This skill is not included in your current plan. |
| PLAN_ELIGIBILITY_UNAVAILABLE | 503 | Yes | Plan eligibility could not be decided. Retry after backoff. |
| SKILL_NOT_FOUND | 404 | No | The requested skill or version does not exist. |
| TENANT_RATE_LIMITED | 429 | Yes | Request rate limit exceeded. Retry after backoff. |
| QUOTA_EXCEEDED | 429 | Yes | Plan quota exhausted. Retryable once the quota period resets. |
| EXECUTION_TIMEOUT | 504 | Yes | Execution exceeded the allowed time budget. |
| GUARD_TEXT_INVALID | 400 | No | The request payload failed this skill's input validation. |
| GUARD_TEXT_TOO_LARGE | 429 | No | A size, quota, rate, or time limit was exceeded. |
| GUARD_EXTRACTION_UNSUPPORTED | 400 | No | The skill does not support the requested variant. |
| GUARD_MULTIPLE_CANDIDATES | 412 | No | The input is valid but cannot be processed as-is. |
| GUARD_DEPTH_EXCEEDED | 429 | No | A size, quota, rate, or time limit was exceeded. |
| GUARD_CANDIDATE_LIMIT_EXCEEDED | 429 | No | A size, quota, rate, or time limit was exceeded. |
| GUARD_OUTPUT_TOO_LARGE | 429 | No | A size, quota, rate, or time limit was exceeded. |
Other limits
| Limit | Value |
|---|---|
| Recommended inline bytes | 32 KiB |
Limitations
- Does not validate against a JSON Schema — schema.validate_json is the platform's only validator
- Does not repair malformed JSON — schema.repair_json does that, and the error code here tells you when to go there
- Does not call a model or retry one for you
- Does not accept relaxed JSON: comments, trailing commas, single quotes, unquoted keys, NaN and Infinity are all refused by name
- Does not reformat or pretty-print; you get the parsed value, not new text
- Checking a payload against a JSON Schema, which belongs to schema.validate_json
- Fixing broken JSON rather than diagnosing it
- Parsing a payload you already know is clean JSON, where your own parser is cheaper
How to call
POST /v1/execute (requires authentication)
{
"skillKey": "ai.output_json_guard",
"input": {
"expect": "OBJECT",
"text": "Sure — here is the classification you asked for.\n\n```json\n{\"category\": \"billing\", \"confidence\": 0.82, \"tags\": [\"invoice\", \"refund\"]}\n```\n\nLet me know if you would like it broken down further."
}
}Examples
A fenced block wrapped in prose, which is what most models actually send
{
"expect": "OBJECT",
"text": "Sure — here is the classification you asked for.\n\n```json\n{\"category\": \"billing\", \"confidence\": 0.82, \"tags\": [\"invoice\", \"refund\"]}\n```\n\nLet me know if you would like it broken down further."
}{
"json": {
"category": "billing",
"confidence": 0.82,
"tags": [
"invoice",
"refund"
]
},
"span": {
"start": 58,
"end": 132
},
"errors": [],
"valid": true,
"candidateCount": 1,
"trailingTextPresent": true,
"summary": {
"inputChars": 191,
"jsonChars": 74,
"depth": 2,
"expect": "OBJECT"
},
"extractedBy": "FENCED_JSON"
}Nearly-JSON with a trailing comma, diagnosed by name
{
"text": "{\n \"category\": \"billing\",\n \"tags\": [\"invoice\", \"refund\",],\n}"
}{
"valid": false,
"trailingTextPresent": false,
"candidateCount": 0,
"extractedBy": "NONE",
"summary": {
"inputChars": 62,
"jsonChars": 0,
"depth": 0,
"expect": "ANY"
},
"errors": [
{
"code": "TRAILING_COMMA",
"message": "A comma sits before the closing bracket with no value after it; strict JSON has no trailing commas.",
"line": 3,
"column": 32,
"pointer": "/tags/2"
}
],
"span": {
"start": 0,
"end": 0
}
}Recommended use cases
- Turn a chatty model's fenced code block into a value your code can use
- Decide whether to retry the model or route the reply to schema.repair_json
- Enforce a JSON-only response contract and get the position of anything that breaks it
Avoid for
- Checking a payload against a JSON Schema, which belongs to schema.validate_json
- Fixing broken JSON rather than diagnosing it
- Parsing a payload you already know is clean JSON, where your own parser is cheaper
Related skills
Composable with
Safety & privacy
- Runs without calling any external service.
- Your payloads are never stored permanently.
- Telemetry captures metadata only — never your payload contents.
- We never train models on your payloads.
- No human reviews your payloads.