Skip to content

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/validate
Authorization: Bearer glr_sk_…
Content-Type: application/json

Use it before a batch, or whenever the payload is assembled from something you do not control.

ParameterInDescription
refpathinvoice for the latest version, invoice@3 to pin one.
FieldTypeRequiredDescription
dataunknownnoThe payload to check. Defaults to {}, which is a quick way to ask “what does this template require?”.

200 OK whether or not the payload is valid. This endpoint does not use HTTP status to report validity — read valid.

FieldTypeDescription
objectstringAlways validation.
templatestringThe resolved reference, always with a version: invoice@1.
validbooleantrue when the payload satisfies the schema.
errorsarrayField errors. Empty when valid is true.
exampleunknown | nullThe example payload stored with this template version, if it has one.

Each entry in errors:

FieldTypeDescription
pathstringDotted path into the payload, rooted at data: data.line_items[1].quantity.
messagestringOne sentence naming the path and the problem.
expectedstringThe type or constraint the field must satisfy, e.g. number <= 1, one of ["USD","EUR"], string (format: email).
receivedstringA short description of what was there: missing, null, string, array(3).
exampleunknownA 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.

Validate before rendering shell
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
    }
  }'
Validate before rendering javascript
// 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);
Validate before rendering python
# 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"])
Validate before rendering — no key shell + json
# 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
        }
      }
    }
  }'
200 OK
{
"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.

Validate a good payload
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 }]
}
}'
200 OK
{
"object": "validation",
"template": "invoice@1",
"valid": true,
"errors": [],
"example": { "invoice_number": "INV-1042", "issued_on": "2026-09-16" }
}
  • A template with no schema validates everything. A version published without schema stores {}, which accepts any payload. valid: true then means only “nothing was checked”.
  • Defaults are applied during validation. The validator fills in default values 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 not 2.
  • A valid payload can still fail to render — a broken template, an unreachable asset or a browser timeout are render_failed and asset_blocked, not validation problems.
TypeStatusWhen
not_found404No such template on this account, or no such version.
invalid_request400Body is not JSON, or the reference is malformed.
authentication_error401Missing, 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.