Template gallery / progress-report
Client progress report template
Client progress report written like a document a coach signed rather than a list a machine produced: the verdict as a lead paragraph beside the block's numbers as a ruled key, a start / now / change table grouped by measurement type whose change column carries the word better or worse beside its sign, the goals set last time and what happened to each, the coach's notes as real prose at a readable measure, what went well beside what to watch, the next block as a tick list, and an optional sign-off that never orphans.
chromium default PDF 20 top-level fields Billed per page
Fields
The full JSON Schema is below. Required fields are marked; anything missing comes back as a 422 naming the path, the expected type and a value that would be accepted.
| Field | Type | Required | Description |
|---|---|---|---|
| accent | string | optional | The one hot accent: a worse change, the "what to watch" rule and the rule over the client quote. A hex colour or a CSS colour keyword. It is interpolated into the stylesheet, so nothing else is accepted. |
| title | string | optional | |
| client | object { name, programme } | required | |
| coach | object { name, business, email, logo_url } | optional | |
| period | object { label, from, to } | required | |
| headline | string | optional | The one paragraph they will read if they read nothing else. Say the honest version. |
| summary_cards | array of object { label, value, note } | optional | The four or five numbers that summarise the block, printed as a ruled key beside the lead paragraph rather than as a row of boxes. |
| metrics_heading | string | optional | |
| before_label | string | optional | |
| after_label | string | optional | |
| groups | array of object { title, metrics } | optional | Measurements grouped into sections. Values are strings so you can carry units, times and reps in the same table without the template pretending to do maths on them. |
| goals | array of object { goal, status, direction, progress, … } | optional | What was agreed last time, and what happened. |
| notes_heading | string | optional | |
| coach_notes | array of string | optional | |
| quote | object { text, who } | optional | Something the client said, in their own words. |
| wins | array of string | optional | |
| watch | array of string | optional | |
| next_block | object { name, dates, summary, targets } | optional | |
| sign_off | boolean | optional | Adds two signature lines, for programmes where the client signs off on the review. |
| footer | string | optional | Replaces the default measurement-conditions line. |
Default render options
Applied unless you override them. Options are part of the cache key.
| page_size | "Letter" |
| margin | "0.6in" |
| print_background | true |
Example payload
This exact object renders the preview above. It is also what the API echoes back in a validation error, so an agent can repair a payload without a round trip to the docs.
{
"title": "Twelve-week progress report",
"client": {
"name": "Marcus Oyelaran",
"programme": "Strength and conditioning, two sessions a week"
},
"coach": {
"name": "Dana Whitfield-Orr",
"business": "Northbank Strength",
"email": "dana@northbankstrength.test"
},
"period": {
"label": "Block 2 · weeks 13–24",
"from": "2026-06-22",
"to": "2026-09-13"
},
"headline": "Twenty-one of twenty-four sessions attended, up from seventeen last block, and every main lift is up. The squat is the s… [343 characters, shortened for display]",
"summary_cards": [
{
"label": "Sessions",
"value": "21 / 24",
"note": "88% attendance, up from 71%"
},
{
"label": "Total load lifted",
"value": "412,000 lb",
"note": "Up 31% on block 1"
},
{
"label": "Bodyweight",
"value": "196 lb",
"note": "Down 4 lb from 200"
},
{
"label": "Resting HR",
"value": "58 bpm",
"note": "Down 7 from 65"
}
],
"before_label": "Jun 22",
"after_label": "Sep 13",
"groups": [
{
"title": "Strength",
"metrics": [
{
"name": "Back squat, 5RM",
"unit": "lb",
"before": "185",
"after": "235",
"change": "+50",
"direction": "better",
"note": "Depth improved at the same time, which makes the jump bigger than it looks"
},
{
"name": "Deadlift, 3RM",
"unit": "lb",
"before": "275",
"after": "315",
"change": "+40",
"direction": "better",
"note": "Bar path is straighter; you stopped rounding at the knee"
},
{
"name": "Bench press, 5RM",
"unit": "lb",
"before": "155",
"after": "175",
"change": "+20",
"direction": "better",
"note": "Slowest of the three, and expected given the shoulder"
},
{
"name": "Overhead press, 5RM",
"unit": "lb",
"before": "95",
"after": "105",
"change": "+10",
"direction": "better",
"note": "Held back deliberately while the left shoulder settled"
},
{
"name": "Chin-ups, best set",
"unit": "reps",
"before": "4",
"after": "9",
"change": "+5",
"direction": "better",
"note": "Full range, no kipping"
}
]
},
{
"title": "Body composition",
"metrics": [
{
"name": "Bodyweight",
"unit": "lb",
"before": "200",
"after": "196",
"change": "−4",
"direction": "better",
"note": "Same scale, Tuesday mornings, before breakfast"
},
{
"name": "Waist",
"unit": "in",
"before": "36.5",
"after": "35.0",
"change": "−1.5",
"direction": "better",
"note": "Measured at the navel, standing relaxed"
},
{
"name": "Chest",
"unit": "in",
"before": "41.0",
"after": "42.0",
"change": "+1.0",
"direction": "better"
},
{
"name": "Upper arm, right",
"unit": "in",
"before": "13.5",
"after": "14.25",
"change": "+0.75",
"direction": "better"
}
]
},
{
"title": "Movement and conditioning",
"metrics": [
{
"name": "Overhead squat screen",
"unit": "0–3",
"before": "1",
"after": "2",
"change": "+1",
"direction": "better",
"note": "Heels stay down now; arms still drift forward at the bottom"
},
{
"name": "Left shoulder flexion",
"unit": "deg",
"before": "155",
"after": "168",
"change": "+13",
"direction": "better",
"note": "Right side is 178, so there is still a gap"
},
{
"name": "500 m row",
"unit": "time",
"before": "1:52",
"after": "1:44",
"change": "−8 s",
"direction": "better"
},
{
"name": "Sleep, self-reported",
"unit": "h/night",
"before": "6.2",
"after": "6.4",
"change": "+0.2",
"note": "Barely moved. This is the one we did not fix"
}
]
}
],
"goals": [
{
"goal": "Squat 225 lb for five",
"status": "Met",
"direction": "better",
"progress": 100,
"note": "Hit 235 in week 21, two weeks ahead of schedule"
},
{
"goal": "Ten unbroken chin-ups",
"status": "Nearly",
"progress": 90,
"note": "Nine in week 23. Tenth is a formality in block 3"
},
{
"goal": "Attend 22 of 24 sessions",
"status": "Missed by one",
"progress": 88,
"note": "Three missed: two work trips and one you told me about in advance, which counts for something"
},
{
"goal": "Seven hours of sleep a night",
"status": "Not met",
"direction": "worse",
"progress": 30,
"note": "6.4 on average. We have talked about this more than any lift and it is still the biggest lever you are not pulling"
}
],
"coach_notes": [
"The attendance is the real story of this block. Seventeen sessions became twenty-one, and everything in the table above … [244 characters, shortened for display]",
"The squat progression worked because we stopped chasing the number in week 14 and spent three weeks at 185 fixing the depth. That felt like standing still at the time. It was not: the 235 in week 21 was a better rep than the 185 in week 13.",
"The shoulder is the thing I want to keep an eye on. It is better — thirteen degrees of flexion back and no pain reported… [401 characters, shortened for display]",
"Sleep is the one thing that did not move. Six point four hours will cap what block 3 can do, and it is cheaper than anyt… [261 characters, shortened for display]"
],
"quote": {
"text": "I carried both kids up the stairs at once last week and did not think about it until afterwards. That has not been true for about three years.",
"who": "Marcus, week 19"
},
"wins": [
"Every main lift up, and none of them bought with worse technique",
"Attendance up seventeen points, which is the reason for everything else",
"Four pounds down and an inch and a half off the waist while the lifts went up",
"No missed sessions at all in weeks 17 to 24",
"Shoulder pain gone since week 18"
],
"watch": [
"Left shoulder is still thirteen degrees short of the right overhead",
"Sleep has not moved and is now the limiting factor",
"Bench is the slowest lift and will stay that way until the shoulder closes the gap",
"Two of the three missed sessions were the same Tuesday work slot; consider moving it",
"Arms still drift forward at the bottom of the overhead squat"
],
"next_block": {
"name": "Block 3 — strength with a hypertrophy bias",
"dates": "Sep 21 – Dec 13, 2026",
"summary": "The same two committed sessions a week, with a third optional day that is yours to take or leave. More volume per session, overhead work added back gradually, and the first deload in week 30 rather than week 34.",
"targets": [
"Back squat 255 lb for five by week 36",
"Ten unbroken chin-ups, which is one rep away",
"Overhead press 120 lb for five, only if the shoulder stays quiet",
"Left shoulder flexion within five degrees of the right",
"Six and a half hours of sleep on at least five nights a week, tracked honestly",
"Attendance of 22 of 24 again, with the Tuesday slot moved to Wednesday"
]
},
"sign_off": true,
"footer": "Measurements were taken under the same conditions each time: same scale on Tuesday mornings before breakfast, same tape … [305 characters, shortened for display]"
}
Values longer than 240 characters — data URIs, inline SVG — are shortened here so the
shape of the payload stays readable, and the replacement says how much was left out.
get_template and GET /v1/templates/:ref return them in full.
Render it
curl -sS https://api.galleyrender.com/v1/render \
-H "Authorization: Bearer $GALLEY_API_KEY" \
-H 'content-type: application/json' \
-d '{
"template": "progress-report@1",
"format": "pdf",
"data": "… the example payload above …"
}' # No API key. The first call mints a trial of 10 PDF pages or 10 images.
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": "render",
"arguments": {
"template": "progress-report",
"format": "pdf",
"data": { … see the example payload above … }
}
}
}'
Pin the version — progress-report@1 — in anything you ship. Publishing a new
version never changes an old one. Render endpoint reference →
Renders the example payload on the production service. No account, no key.
JSON Schema
schema.json (20 top-level properties)
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"title": "Client progress report",
"type": "object",
"required": [
"client",
"period"
],
"additionalProperties": true,
"properties": {
"accent": {
"type": "string",
"description": "The one hot accent: a worse change, the \"what to watch\" rule and the rule over the client quote. A hex colour or a CSS colour keyword. It is interpolated into the stylesheet, so nothing else is accepted.",
"examples": [
"#d62828"
],
"default": "#d62828",
"pattern": "^(#[0-9a-fA-F]{3,8}|[a-zA-Z]{3,20})$"
},
"title": {
"type": "string",
"default": "Progress report",
"examples": [
"Twelve-week progress report"
]
},
"client": {
"type": "object",
"required": [
"name"
],
"properties": {
"name": {
"type": "string",
"examples": [
"Marcus Oyelaran"
]
},
"programme": {
"type": "string",
"examples": [
"Strength and conditioning, two sessions a week"
]
}
}
},
"coach": {
"type": "object",
"properties": {
"name": {
"type": "string",
"examples": [
"Dana Whitfield-Orr"
]
},
"business": {
"type": "string",
"examples": [
"Northbank Strength"
]
},
"email": {
"type": "string",
"format": "email",
"examples": [
"dana@northbankstrength.test"
]
},
"logo_url": {
"type": "string",
"format": "uri",
"examples": [
"https://example.com/northbank.png"
]
}
}
},
"period": {
"type": "object",
"required": [
"label"
],
"properties": {
"label": {
"type": "string",
"examples": [
"Block 2 · weeks 13–24"
]
},
"from": {
"type": "string",
"format": "date",
"examples": [
"2026-06-22"
]
},
"to": {
"type": "string",
"format": "date",
"examples": [
"2026-09-13"
]
}
}
},
"headline": {
"type": "string",
"description": "The one paragraph they will read if they read nothing else. Say the honest version.",
"examples": [
"Twelve weeks, twenty-one of twenty-four sessions attended, and every main lift up."
]
},
"summary_cards": {
"type": "array",
"description": "The four or five numbers that summarise the block, printed as a ruled key beside the lead paragraph rather than as a row of boxes.",
"items": {
"type": "object",
"required": [
"label",
"value"
],
"properties": {
"label": {
"type": "string",
"examples": [
"Sessions"
]
},
"value": {
"type": "string",
"examples": [
"21 / 24"
]
},
"note": {
"type": "string",
"examples": [
"88% attendance, up from 71%"
]
}
}
}
},
"metrics_heading": {
"type": "string",
"default": "Where you started, where you are",
"examples": [
"Where you started, where you are"
]
},
"before_label": {
"type": "string",
"default": "Start",
"examples": [
"Jun 22"
]
},
"after_label": {
"type": "string",
"default": "Now",
"examples": [
"Sep 13"
]
},
"groups": {
"type": "array",
"description": "Measurements grouped into sections. Values are strings so you can carry units, times and reps in the same table without the template pretending to do maths on them.",
"items": {
"type": "object",
"required": [
"title",
"metrics"
],
"properties": {
"title": {
"type": "string",
"examples": [
"Strength"
]
},
"metrics": {
"type": "array",
"items": {
"type": "object",
"required": [
"name",
"before",
"after"
],
"properties": {
"name": {
"type": "string",
"examples": [
"Back squat, 5RM"
]
},
"unit": {
"type": "string",
"examples": [
"lb"
]
},
"before": {
"type": "string",
"examples": [
"185"
]
},
"after": {
"type": "string",
"examples": [
"235"
]
},
"change": {
"type": "string",
"description": "Write the change yourself, with its sign. A drop in resting heart rate is an improvement and a drop in squat is not; only you know which.",
"examples": [
"+50"
]
},
"direction": {
"type": "string",
"description": "Marks the change better or worse. The word is printed under the figure as well as colouring it, so the distinction survives a black-and-white printer. Leave it off for neutral.",
"enum": [
"",
"better",
"worse"
],
"examples": [
"better"
]
},
"note": {
"type": "string",
"examples": [
"Depth improved at the same time, which makes the jump bigger than it looks"
]
}
}
}
}
}
}
},
"goals": {
"type": "array",
"description": "What was agreed last time, and what happened.",
"items": {
"type": "object",
"required": [
"goal",
"status"
],
"properties": {
"goal": {
"type": "string",
"examples": [
"Squat 225 lb for five"
]
},
"status": {
"type": "string",
"examples": [
"Met"
]
},
"direction": {
"type": "string",
"enum": [
"",
"better",
"worse"
],
"examples": [
"better"
]
},
"progress": {
"type": "number",
"minimum": 0,
"maximum": 100,
"description": "Draws a bar under the status.",
"examples": [
100
]
},
"note": {
"type": "string",
"examples": [
"Hit 235 in week 21, two weeks ahead"
]
}
}
}
},
"notes_heading": {
"type": "string",
"default": "Coach's notes",
"examples": [
"Coach's notes"
]
},
"coach_notes": {
"type": "array",
"items": {
"type": "string",
"examples": [
"The attendance is the story of this block."
]
},
"examples": [
[
"The attendance is the story of this block."
]
]
},
"quote": {
"type": "object",
"description": "Something the client said, in their own words.",
"properties": {
"text": {
"type": "string",
"examples": [
"I carried both kids up the stairs at once last week and did not think about it until afterwards."
]
},
"who": {
"type": "string",
"examples": [
"Marcus, week 19"
]
}
}
},
"wins": {
"type": "array",
"items": {
"type": "string",
"examples": [
"Every main lift up, none of them at the cost of technique"
]
},
"examples": [
[
"Every main lift up"
]
]
},
"watch": {
"type": "array",
"items": {
"type": "string",
"examples": [
"Left shoulder still clicks overhead; it has not got worse but it has not gone"
]
},
"examples": [
[
"Left shoulder still clicks overhead"
]
]
},
"next_block": {
"type": "object",
"properties": {
"name": {
"type": "string",
"examples": [
"Block 3 — strength with a hypertrophy bias"
]
},
"dates": {
"type": "string",
"examples": [
"Sep 21 – Dec 13, 2026"
]
},
"summary": {
"type": "string",
"examples": [
"Same two sessions a week, more volume per session and a third optional day."
]
},
"targets": {
"type": "array",
"items": {
"type": "string",
"examples": [
"Back squat 255 for five by week 36"
]
},
"examples": [
[
"Back squat 255 for five"
]
]
}
}
},
"sign_off": {
"type": "boolean",
"default": false,
"description": "Adds two signature lines, for programmes where the client signs off on the review.",
"examples": [
true
]
},
"footer": {
"type": "string",
"description": "Replaces the default measurement-conditions line.",
"examples": [
"Measurements were taken under the same conditions each time."
]
}
}
}