POST /v1/templates/:ref/validate
Checks a payload against a template version’s JSON Schema without rendering anything. It costs no
renders, consumes no quota, and returns exactly the field errors
POST /v1/render would have returned.
POST https://api.galleyrender.com/v1/templates/:ref/validateAuthorization: Bearer glr_sk_…Content-Type: application/jsonUse it before a batch, or whenever the payload is assembled from something you do not control.
Request
Section titled “Request”| Parameter | In | Description |
|---|---|---|
ref | path | invoice for the latest version, invoice@3 to pin one. |
| Field | Type | Required | Description |
|---|---|---|---|
data | unknown | no | The payload to check. Defaults to {}, which is a quick way to ask “what does this template require?”. |
Response
Section titled “Response”200 OK whether or not the payload is valid. This endpoint does not use HTTP status to report
validity — read valid.
| Field | Type | Description |
|---|---|---|
object | string | Always validation. |
template | string | The resolved reference, always with a version: invoice@1. |
valid | boolean | true when the payload satisfies the schema. |
errors | array | Field errors. Empty when valid is true. |
example | unknown | null | The example payload stored with this template version, if it has one. |
Each entry in errors:
| Field | Type | Description |
|---|---|---|
path | string | Dotted path into the payload, rooted at data: data.line_items[1].quantity. |
message | string | One sentence naming the path and the problem. |
expected | string | The type or constraint the field must satisfy, e.g. number <= 1, one of ["USD","EUR"], string (format: email). |
received | string | A short description of what was there: missing, null, string, array(3). |
example | unknown | A value that would be accepted. Present when the schema (or its format) gives one. |
Errors are deduplicated by path and keyword, so one bad field produces one entry per distinct rule it broke.
Examples
Section titled “Examples”A payload that is wrong
Section titled “A payload that is wrong”curl -sS https://api.galleyrender.com/v1/templates/invoice@1/validate \
-H "Authorization: Bearer $GALLEY_API_KEY" \
-H 'content-type: application/json' \
-d '{
"data": {
"invoice_number": "INV-1042",
"seller": { "name": "Galley Render" },
"buyer": { "name": "Acme Robotics" },
"line_items": [
{ "description": "September", "quantity": "two", "unit_price": 19 }
],
"tax_rate": 1.5
}
}' // Node 22+. No dependencies — `fetch` is built in.
const res = await fetch("https://api.galleyrender.com/v1/templates/invoice@1/validate", {
method: "POST",
headers: {
authorization: `Bearer ${process.env.GALLEY_API_KEY}`,
"content-type": "application/json",
},
body: JSON.stringify({
data: {
invoice_number: "INV-1042",
seller: {
name: "Galley Render"
},
buyer: {
name: "Acme Robotics"
},
line_items: [
{
description: "September",
quantity: "two",
unit_price: 19
}
],
tax_rate: 1.5
}
}),
});
const result = await res.json();
if (!res.ok) throw new Error(result.error.message);
console.log(result.valid); # Python 3.9+. Standard library only.
import json, os, urllib.request
body = json.dumps({
"data": {
"invoice_number": "INV-1042",
"seller": {
"name": "Galley Render"
},
"buyer": {
"name": "Acme Robotics"
},
"line_items": [
{
"description": "September",
"quantity": "two",
"unit_price": 19
}
],
"tax_rate": 1.5
}
}).encode()
req = urllib.request.Request(
"https://api.galleyrender.com/v1/templates/invoice@1/validate",
data=body,
headers={
"Authorization": f"Bearer {os.environ['GALLEY_API_KEY']}",
"Content-Type": "application/json",
},
)
result = json.load(urllib.request.urlopen(req))
print(result["valid"]) # No API key. The first call mints a 50-render trial.
curl -sS https://mcp.galleyrender.com/mcp \
-H 'content-type: application/json' \
-H 'accept: application/json, text/event-stream' \
-d '{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "validate_data",
"arguments": {
"template": "invoice@1",
"data": {
"invoice_number": "INV-1042",
"seller": {
"name": "Galley Render"
},
"buyer": {
"name": "Acme Robotics"
},
"line_items": [
{
"description": "September",
"quantity": "two",
"unit_price": 19
}
],
"tax_rate": 1.5
}
}
}
}' { "object": "validation", "template": "invoice@1", "valid": false, "errors": [ { "path": "data.issued_on", "message": "data.issued_on is required", "expected": "string (required)", "received": "missing", "example": "2026-09-16" }, { "path": "data.line_items[0].quantity", "message": "data.line_items[0].quantity must be number", "expected": "number", "received": "string", "example": 2 }, { "path": "data.tax_rate", "message": "data.tax_rate must be <= 1", "expected": "number <= 1", "received": "number", "example": 0.07 } ], "example": { "invoice_number": "INV-1042", "issued_on": "2026-09-16", "seller": { "name": "Galley Render" }, "buyer": { "name": "Acme Robotics" }, "line_items": [{ "description": "Starter plan, September", "quantity": 1, "unit_price": 19 }], "tax_rate": 0.07 }}Fix each path using expected and example, then render. Do not guess: the schema is the
contract and it is readable at GET /v1/templates/:ref.
A payload that is right
Section titled “A payload that is right”curl -sS https://api.galleyrender.com/v1/templates/invoice@1/validate \ -H "Authorization: Bearer $GALLEY_API_KEY" \ -H 'content-type: application/json' \ -d '{ "data": { "invoice_number": "INV-1042", "issued_on": "2026-09-16", "seller": { "name": "Galley Render" }, "buyer": { "name": "Acme Robotics" }, "line_items": [{ "description": "September", "quantity": 1, "unit_price": 19 }] } }'{ "object": "validation", "template": "invoice@1", "valid": true, "errors": [], "example": { "invoice_number": "INV-1042", "issued_on": "2026-09-16" }}What validation does not cover
Section titled “What validation does not cover”- A template with no schema validates everything. A version published without
schemastores{}, which accepts any payload.valid: truethen means only “nothing was checked”. - Defaults are applied during validation. The validator fills in
defaultvalues from the schema, so a missing field with a default is not an error and the render sees the default. - Types are never coerced.
"2"is not2. - A valid payload can still fail to render — a broken template, an unreachable asset or a
browser timeout are
render_failedandasset_blocked, not validation problems.
Errors
Section titled “Errors”| Type | Status | When |
|---|---|---|
not_found | 404 | No such template on this account, or no such version. |
invalid_request | 400 | Body is not JSON, or the reference is malformed. |
authentication_error | 401 | Missing, unknown or revoked key. |
Note the asymmetry: an invalid payload is a 200 with valid: false here, and a 422
validation_error at render time.