{"name":"com.galleyrender/galley-render","slug":"galleyrender-galley-render","title":"Galley Render","description":"JSON in, PDF out. Render invoices, certificates, reports and cards from a template and a payload.","url":"https://mcp.market/server/galleyrender-galley-render","rating":null,"grade":"C","score":60,"certified":false,"status":"active","category":"other","tags":[],"presence":{"score":8,"stars":null,"forks":null,"downloads_week":null,"last_push_at":null,"license":null},"uptime":{"percent":100,"checks":1,"ok":1,"last_checked_at":"2026-09-19T15:40:41.567Z","last_ok_at":"2026-09-19T15:40:41.567Z","latency_ms":728},"claimed":false,"transport":"remote","callable_via_gateway":true,"default_price_micros":0,"repository":null,"website":"https://galleyrender.com","version":"0.1.0","remotes":[{"type":"streamable-http","url":"https://mcp.galleyrender.com/mcp"}],"packages":[],"tools":[{"name":"billing_portal","description":"Get a link to the Stripe customer portal, for a human to open: change plan, update the card, download invoices, or cancel.\n\nThis is the only way any of those happen. Galley's API cannot change or cancel a subscription and cannot issue a refund — deliberately — so if you are asked to downgrade or cancel, the answer is this link and a human on the other end of it. The link is single-use and short-lived, so fetch a fresh one rather than storing it.\n\nOnly works once the account has subscribed at least once; before that, use `upgrade`. Free.\n\n**If billing is not enabled on the deployment this is pointed at, the call is refused with `billing_unavailable` (503) rather than answered with a link.** Say so and point the human at support@galleyrender.com. Do not construct a portal URL yourself and do not retry.","write_action":true,"price_micros":0,"input_schema":{"type":"object","properties":{},"$schema":"http://json-schema.org/draft-07/schema#"}},{"name":"create_account","description":"Turn the keyless trial into a real account and get a permanent API key.\n\nCall it once with an email: a verification link is sent and the tool returns `status: \"pending_verification\"`. After the human clicks the link, call it again with the same email and it returns the API key, once. The key is not released before the click. The click also binds this client to the new account, so a connector that cannot send headers is connected from then on without anything further.\n\n**If that address already has an account, no second one is made.** You get `status: \"existing_account\"`, `verified: false`, `existing_account: true`, and a one-time code in the post — hand that code to `link_account` and this client is connected to the account that already exists. Use this rather than inventing a second address: two accounts means two free tiers and two sets of templates, and the renders anyone is waiting for are on the first one.\n\nThe trial in this session is upgraded in place, so templates and renders made during the trial are kept. The free tier is 20 renders a month; pricing beyond that is at https://galleyrender.com/pricing. Store the key as a secret — it is shown only on that one call.","write_action":true,"price_micros":0,"input_schema":{"type":"object","properties":{"email":{"type":"string","description":"Where to send the verification link. Use the address of the person who owns this project — not a made-up one; the key is only released after the link is clicked. If this address already has a Galley account, no second one is made: a one-time link code is mailed to it instead, and `link_account` takes that code.","examples":["dev@example.com"]},"name":{"description":"Account name. Defaults to the local part of the email.","type":"string"}},"required":["email"],"$schema":"http://json-schema.org/draft-07/schema#"}},{"name":"create_template","description":"Create a new template at version 1 from an HTML document with Liquid expressions, plus a JSON Schema for its data. Use this when nothing in list_templates fits. The name must be free on this account — publishing a change to an existing template is `update_template`, not this. Free.","write_action":true,"price_micros":0,"input_schema":{"type":"object","properties":{"name":{"type":"string","pattern":"^[a-z0-9][a-z0-9._-]{0,62}$","description":"Template name: lowercase letters, digits, dot, dash or underscore, 1-63 characters. Unique per account. Versions are separate — do not put `@1` here.","examples":["invoice","delivery-note"]},"source":{"type":"string","minLength":1,"description":"One self-contained HTML document with inline CSS and Liquid expressions (`{{ customer.name }}`, `{% for line in line_items %}`). No file includes: everything the render needs must be in this string, or at a public https URL. Two extra filters ship by default: `money` and `date_medium`. For a PDF, use `@page { size: Letter; margin: 18mm }` to control pagination.","examples":["<html><body><h1>{{ title }}</h1></body></html>"]},"engine":{"description":"Rendering engine. `chromium` (default) is full HTML and CSS and is required for PDF. `satori` is a fast PNG path for simple flexbox card layouts — no page breaks, no floats, no external CSS — and costs us less, so prefer it for OG images and social cards.","type":"string","enum":["chromium","satori"]},"schema":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{},"description":"JSON Schema (2020-12) for the `data` payload this template accepts. Strongly recommended: it is what turns a bad payload into a field-level error with a path, an expected type and a working example instead of a blank page. Use `required` and give each property a `description`.","examples":[{"type":"object","required":["title"],"properties":{"title":{"type":"string","description":"Headline, one line"}}}]},"options":{"type":"object","properties":{"page_size":{"description":"PDF page size: `Letter`, `A4`, `Legal`, or `210mm x 297mm`.","type":"string"},"landscape":{"description":"PDF only. Default false.","type":"boolean"},"margin":{"description":"PDF margins, CSS lengths: `18mm`, or per side.","anyOf":[{"type":"string"},{"type":"object","properties":{"top":{"type":"string"},"right":{"type":"string"},"bottom":{"type":"string"},"left":{"type":"string"}}}]},"width":{"description":"Viewport width in px for png/jpg. Default 1200.","type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"height":{"description":"Viewport height in px for png/jpg. Default 630.","type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"scale":{"description":"Device pixel ratio for png/jpg, 0.1-3. Use 2 for retina.","type":"number","minimum":0.1,"maximum":3},"full_page":{"description":"png/jpg: capture the whole document instead of the viewport.","type":"boolean"},"quality":{"description":"jpg only, 1-100. Default 90.","type":"integer","minimum":1,"maximum":100},"background":{"description":"CSS colour painted under the document, e.g. `#ffffff`.","type":"string"},"print_background":{"description":"PDF: print background colours and images. Default true.","type":"boolean"},"css":{"description":"Extra CSS appended after the template's own styles.","type":"string"},"on_blocked_asset":{"description":"What to do when an image or font the template asks for is refused by the egress policy or fails to fetch. `fail` (the default) fails the render with `asset_blocked` naming the URL; `skip` drops the asset and renders without it. Use `skip` only for decorative assets.","type":"string","enum":["fail","skip"]}},"description":"Render options, merged over the template's own defaults. Options are part of the cache key, so two calls that differ only here are two different renders."},"expected_pages":{"description":"How many PDF pages a typical payload renders. Default 1. This is the estimate the free tier and the spend cap are checked against before the render starts, so a template that runs to several pages must say so or a render that cannot fit the allowance will be started and then go over. It is not a limit: the render is billed on the pages it actually produced.","type":"integer","minimum":1,"maximum":2000},"example":{"description":"A payload that renders correctly. It is echoed back in validation errors, so include one."},"description":{"description":"One line on what this template is for. Shown in list_templates.","type":"string"},"message":{"description":"Change note for this version, like a commit message.","type":"string"}},"required":["name","source"],"$schema":"http://json-schema.org/draft-07/schema#"}},{"name":"get_render","description":"Fetch a render by id: its status, and a freshly signed URL once it has succeeded. Use it to poll a queued render, or to re-sign a URL that has expired — signed URLs are short-lived, the stored object is not. Free, and never re-renders.","write_action":false,"price_micros":0,"input_schema":{"type":"object","properties":{"render_id":{"type":"string","description":"Render id from a previous render call, e.g. `rnd_…`. Returns the status and a fresh signed URL.","examples":["rnd_7hq2m4x8k1bv"]}},"required":["render_id"],"$schema":"http://json-schema.org/draft-07/schema#"}},{"name":"get_template","description":"Fetch one template version: its JSON Schema, its default render options, an example payload and its HTML source. Read the schema before rendering — it is the contract for the `data` argument. Free.","write_action":false,"price_micros":0,"input_schema":{"type":"object","properties":{"template":{"type":"string","minLength":1,"description":"Template to use: `invoice` for the latest version, or `invoice@3` to pin version 3. Pin the version in anything you ship — a new version changes the output and the cache key. Call list_templates to see what this account has.","examples":["invoice","invoice@3"]},"include_source":{"description":"Include the HTML source in the response. Default true. Set false when you only need the schema.","type":"boolean"}},"required":["template"],"$schema":"http://json-schema.org/draft-07/schema#"}},{"name":"link_account","description":"Point this MCP client at an account you already have, so header-less calls from it stop spending the keyless trial.\n\n**This is for clients that cannot set HTTP headers** — Claude.ai custom connectors and ChatGPT apps. If your client *can* set a header, prefer that: it is per-connection, it is not tied to a network address, and it needs no tool call at all.\n\nTwo ways to prove the account is yours. `api_key`, if you have the key to hand. Or `link_code`, the one-time code mailed to the account's own address — call `create_account` with that address to have one sent, and nobody has to paste a key into a chat window.\n\nEither way the credential is used once, checked, and dropped: what is stored is the account id and a key minted for this client, which `unlink_account` revokes.\n\n**The client id is coarse.** Without an `X-Galley-Client-Id` header it is derived from the IP address and the User-Agent, so on a shared machine, or behind one office NAT with the same client, another person can land on the same identity and reach this account. Use a header instead wherever the client allows one, and `unlink_account` when you are done on a machine that is not yours. Free.","write_action":false,"price_micros":0,"input_schema":{"type":"object","properties":{"api_key":{"description":"A Galley API key, starting `glr_sk_`. It is checked against the API and then discarded — the binding that is stored is by account id, and this server keeps no copy of the key. Give either this or `link_code`.","examples":["glr_sk_…"],"type":"string"},"link_code":{"description":"The one-time code mailed to the account's address, like `GLR-4F7K-9QX2`. Ten minutes, one use. Ask for one by calling `create_account` with the address: if it already has an account, a code is what comes back. Use this when the person would rather not paste a key into a chat. Case, spaces and dashes do not matter.","examples":["GLR-4F7K-9QX2"],"type":"string"}},"$schema":"http://json-schema.org/draft-07/schema#"}},{"name":"list_renders","description":"Recent renders on this account, newest first, with status, template version and a signed URL for each that succeeded. Useful for finding a render whose id you lost, or checking what a batch did. Free.","write_action":false,"price_micros":0,"input_schema":{"type":"object","properties":{"limit":{"description":"How many to return, newest first. 1-100, default 25.","type":"integer","minimum":1,"maximum":100}},"$schema":"http://json-schema.org/draft-07/schema#"}},{"name":"list_templates","description":"List the templates on this account, with their latest version number. Start here: rendering needs a template, and this says which ones exist. A brand-new trial account starts with the starter library (invoice, quote, receipt, og-card, certificate and more) already loaded. Free.","write_action":false,"price_micros":0,"input_schema":{"type":"object","properties":{},"$schema":"http://json-schema.org/draft-07/schema#"}},{"name":"render","description":"Render a template plus a JSON payload into a PDF, PNG or JPG and return a signed URL. This is the one tool most calls need.\n\nSmall jobs finish inside the call and come back `status: \"succeeded\"` with a `url` you can hand straight to a user. Anything with a `webhook_url`, `async: true` or a large payload comes back `status: \"queued\"` with an id for get_render.\n\nRenders are deterministic and cached: the same template version, data and options return the stored object with `cached: true`, free and instant. Billing is one unit per PNG or JPG and one per PDF page; cache hits are never billed.\n\nNo API key needed to start — the first call mints a 10-render trial and returns its token.","write_action":false,"price_micros":0,"input_schema":{"type":"object","properties":{"template":{"type":"string","minLength":1,"description":"Template to use: `invoice` for the latest version, or `invoice@3` to pin version 3. Pin the version in anything you ship — a new version changes the output and the cache key. Call list_templates to see what this account has.","examples":["invoice","invoice@3"]},"data":{"description":"The JSON payload for the template, matching its schema. Call get_template for the schema, or validate_data to dry-run a payload for free."},"format":{"description":"Output format. Defaults to `pdf` for chromium templates and `png` for satori ones. `webp` is not supported in v1.","type":"string","enum":["pdf","png","jpg","jpeg"]},"options":{"type":"object","properties":{"page_size":{"description":"PDF page size: `Letter`, `A4`, `Legal`, or `210mm x 297mm`.","type":"string"},"landscape":{"description":"PDF only. Default false.","type":"boolean"},"margin":{"description":"PDF margins, CSS lengths: `18mm`, or per side.","anyOf":[{"type":"string"},{"type":"object","properties":{"top":{"type":"string"},"right":{"type":"string"},"bottom":{"type":"string"},"left":{"type":"string"}}}]},"width":{"description":"Viewport width in px for png/jpg. Default 1200.","type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"height":{"description":"Viewport height in px for png/jpg. Default 630.","type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"scale":{"description":"Device pixel ratio for png/jpg, 0.1-3. Use 2 for retina.","type":"number","minimum":0.1,"maximum":3},"full_page":{"description":"png/jpg: capture the whole document instead of the viewport.","type":"boolean"},"quality":{"description":"jpg only, 1-100. Default 90.","type":"integer","minimum":1,"maximum":100},"background":{"description":"CSS colour painted under the document, e.g. `#ffffff`.","type":"string"},"print_background":{"description":"PDF: print background colours and images. Default true.","type":"boolean"},"css":{"description":"Extra CSS appended after the template's own styles.","type":"string"},"on_blocked_asset":{"description":"What to do when an image or font the template asks for is refused by the egress policy or fails to fetch. `fail` (the default) fails the render with `asset_blocked` naming the URL; `skip` drops the asset and renders without it. Use `skip` only for decorative assets.","type":"string","enum":["fail","skip"]}},"description":"Render options, merged over the template's own defaults. Options are part of the cache key, so two calls that differ only here are two different renders."},"webhook_url":{"description":"Absolute https URL to POST the finished render to. Supplying one forces the queued path: the call returns immediately with status `queued`.","examples":["https://example.com/hooks/galley"],"type":"string"},"async":{"description":"Force the queued path even for a small job. Default false: small jobs finish inside the call and come back with a URL already.","type":"boolean"}},"required":["template","data"],"$schema":"http://json-schema.org/draft-07/schema#"}},{"name":"unlink_account","description":"Undo `link_account`. The binding is deleted and the key that was minted for this client is revoked, so the client can no longer reach the account. Your own API key is untouched — this disconnects a client, it does not close an account.\n\nDo this on any machine that is not yours, and on any client whose identity is a shared one (no `X-Galley-Client-Id` header means IP plus User-Agent). Calls afterwards fall back to the keyless trial. Safe to call when nothing is linked. Free.","write_action":false,"price_micros":0,"input_schema":{"type":"object","properties":{},"$schema":"http://json-schema.org/draft-07/schema#"}},{"name":"update_template","description":"Publish a new immutable version of an existing template. Versions are never edited in place: `invoice@2` keeps rendering exactly as it did, and anything pinned to it is unaffected. Renders cached against the old version stay valid, and the new version starts with a cold cache. `engine`, `schema`, `options` and `example` are inherited from the previous version unless you send them, so a source-only change needs only `template` and `source`. Free.","write_action":true,"price_micros":0,"input_schema":{"type":"object","properties":{"template":{"type":"string","pattern":"^[a-z0-9][a-z0-9._-]{0,62}$","description":"Name of an existing template. A new immutable version is published; earlier versions keep rendering, so anything pinned to `name@2` is unaffected.","examples":["invoice","delivery-note"]},"source":{"type":"string","minLength":1,"description":"One self-contained HTML document with inline CSS and Liquid expressions (`{{ customer.name }}`, `{% for line in line_items %}`). No file includes: everything the render needs must be in this string, or at a public https URL. Two extra filters ship by default: `money` and `date_medium`. For a PDF, use `@page { size: Letter; margin: 18mm }` to control pagination.","examples":["<html><body><h1>{{ title }}</h1></body></html>"]},"engine":{"description":"Engine for the new version. Inherited from the previous version when omitted.","type":"string","enum":["chromium","satori"]},"schema":{"description":"JSON Schema for the `data` payload. Inherited from the previous version when omitted — send `{}` only if you really want a version that validates nothing.","type":"object","propertyNames":{"type":"string"},"additionalProperties":{},"examples":[{"type":"object","required":["title"],"properties":{"title":{"type":"string","description":"Headline, one line"}}}]},"options":{"description":"Default render options. Inherited from the previous version when omitted.","type":"object","properties":{"page_size":{"description":"PDF page size: `Letter`, `A4`, `Legal`, or `210mm x 297mm`.","type":"string"},"landscape":{"description":"PDF only. Default false.","type":"boolean"},"margin":{"description":"PDF margins, CSS lengths: `18mm`, or per side.","anyOf":[{"type":"string"},{"type":"object","properties":{"top":{"type":"string"},"right":{"type":"string"},"bottom":{"type":"string"},"left":{"type":"string"}}}]},"width":{"description":"Viewport width in px for png/jpg. Default 1200.","type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"height":{"description":"Viewport height in px for png/jpg. Default 630.","type":"integer","exclusiveMinimum":0,"maximum":9007199254740991},"scale":{"description":"Device pixel ratio for png/jpg, 0.1-3. Use 2 for retina.","type":"number","minimum":0.1,"maximum":3},"full_page":{"description":"png/jpg: capture the whole document instead of the viewport.","type":"boolean"},"quality":{"description":"jpg only, 1-100. Default 90.","type":"integer","minimum":1,"maximum":100},"background":{"description":"CSS colour painted under the document, e.g. `#ffffff`.","type":"string"},"print_background":{"description":"PDF: print background colours and images. Default true.","type":"boolean"},"css":{"description":"Extra CSS appended after the template's own styles.","type":"string"},"on_blocked_asset":{"description":"What to do when an image or font the template asks for is refused by the egress policy or fails to fetch. `fail` (the default) fails the render with `asset_blocked` naming the URL; `skip` drops the asset and renders without it. Use `skip` only for decorative assets.","type":"string","enum":["fail","skip"]}}},"expected_pages":{"description":"How many PDF pages a typical payload renders. Inherited from the previous version when omitted. This is the estimate the free tier and the spend cap are checked against before the render starts, so a template that runs to several pages must say so or a render that cannot fit the allowance will be started and then go over. It is not a limit: the render is billed on the pages it actually produced.","type":"integer","minimum":1,"maximum":2000},"example":{"description":"A payload that renders correctly under the new version. Inherited when omitted."},"description":{"description":"Replaces the template description.","type":"string"},"message":{"description":"Change note for this version, like a commit message.","type":"string"}},"required":["template","source"],"$schema":"http://json-schema.org/draft-07/schema#"}},{"name":"upgrade","description":"Get a Stripe Checkout link for a paid plan, for a human to open.\n\n**This tool cannot subscribe anybody.** It returns a `url`; a person has to open it and enter a card on Stripe's own page. Hand the URL to the human you are working for and say what it costs. Nothing is charged, and no plan changes, until they finish on that page — at which point Stripe tells Galley and the new plan is live within a second or two. Confirm with `usage`.\n\nUse it when a render was refused with `quota_exceeded`, or with `plan_required` because the account is on Free and asked for webhooks or cloud delivery. The error body names the plan that would have worked.\n\nThe account needs a verified email address first — invoices and receipts go to it — so call `create_account` before this if you are on the keyless trial. Changing or cancelling an existing plan is `billing_portal`, never this. Free.\n\n**If billing is not enabled on the deployment this is pointed at, the call is refused with `billing_unavailable` (503) rather than answered with a link.** That is deliberate: the alternative is a URL on a domain that does not resolve, which you cannot tell apart from a real one. On that error, say so and point the human at https://galleyrender.com/pricing. Do not construct a checkout URL yourself and do not retry.","write_action":false,"price_micros":0,"input_schema":{"type":"object","properties":{"plan":{"type":"string","enum":["solo","starter","growth","scale"],"description":"Which paid plan to subscribe to. Solo $5/mo (500 renders, webhooks, cloud delivery), Starter $19/mo (5,000), Growth $79/mo (30,000, priority queue, 90-day retention), Scale $249/mo (150,000, dedicated pool, SLA). Call `usage` first if you are not sure which one the current volume needs. There is no `free`: moving down a plan is a cancellation in the billing portal, which is `billing_portal`.","examples":["solo"]},"interval":{"description":"Billing interval. Default `month`. `year` is ten months for twelve on every plan.","type":"string","enum":["month","year"]}},"required":["plan"],"$schema":"http://json-schema.org/draft-07/schema#"}},{"name":"usage","description":"What this account has spent this period and what is left: renders, billable units by format, cost, the free-tier allowance, the monthly spend cap and — on a keyless trial — how many of the 50 trial renders remain. Check it before a large batch. Free.","write_action":false,"price_micros":0,"input_schema":{"type":"object","properties":{},"$schema":"http://json-schema.org/draft-07/schema#"}},{"name":"validate_data","description":"Dry-run a `data` payload against a template's JSON Schema and get back exactly the errors a render would raise — field path, expected type, what was received and a value that would be accepted. Costs nothing and renders nothing. Use it before a batch, or whenever you are assembling a payload from somewhere you do not control.","write_action":true,"price_micros":0,"input_schema":{"type":"object","properties":{"template":{"type":"string","minLength":1,"description":"Template to use: `invoice` for the latest version, or `invoice@3` to pin version 3. Pin the version in anything you ship — a new version changes the output and the cache key. Call list_templates to see what this account has.","examples":["invoice","invoice@3"]},"data":{"description":"The payload you intend to render. Checked against the template's JSON Schema and nothing else."}},"required":["template","data"],"$schema":"http://json-schema.org/draft-07/schema#"}},{"name":"whoami","description":"The account this connection is acting on: its id, its plan, how many renders are left, and — the part no other tool answers — **how this request authenticated**: `header` (a key on the HTTP connection), `binding` (this client was linked with `link_account`), or `trial` (the keyless ten-render trial).\n\nCall it before a batch, and call it the moment anything about quota surprises you. A `quota_exceeded` that quotes a limit you do not recognise almost always means `trial`: the key never reached this server, and the renders are coming out of a throwaway account rather than yours. Free, and it renders nothing.","write_action":false,"price_micros":0,"input_schema":{"type":"object","properties":{},"$schema":"http://json-schema.org/draft-07/schema#"}}],"scan":{"score":60,"grade":"C","scanned_at":"2026-09-19T09:58:28.536Z","report":{"scannerVersion":"0.1.3","scannedAt":"2026-09-19T09:58:28.481Z","components":{"code":{"score":-1,"max":25,"notes":["remote-only server, no package to scan"]},"reliability":{"score":20,"max":20,"notes":["remote reachable in 839ms"]},"poisoning":{"score":15,"max":15,"notes":["15 tool descriptions checked"]},"auth":{"score":3,"max":15,"notes":["open endpoint exposes 5 write-action tools with no auth"]},"maintenance":{"score":3,"max":15,"notes":["no repository listed"]},"identity":{"score":4,"max":10,"notes":["verified namespace with website, no repo"]}},"findings":[{"id":"auth.open-write","severity":"high","component":"auth","title":"Write-action tools reachable without authentication"},{"id":"maint.no-repo","severity":"low","component":"maintenance","title":"No source repository listed"}],"inputs":{"probes":[{"url":"https://mcp.galleyrender.com/mcp","reachable":true,"authRequired":false,"latencyMs":839,"serverInfo":{"name":"galley-render","version":"0.1.0"}}],"packages":[],"repo":{"found":false},"icon":{"url":"https://galleyrender.com/favicon.svg","source":"site"},"presence":{"stars":null,"forks":null,"downloadsWeek":null,"license":null,"lastPushAt":null,"score":8}}}},"grade_history":[],"reviews":[]}