Skip to content

Custom Templates API

Custom templates let you save your own Mustache HTML templates for reuse across multiple PDF generations. Once saved, reference them by ID in the /v1/create endpoint or use them with the batch generation API.

MethodEndpointDescription
POST/v1/templates/savedSave a new template
GET/v1/templates/savedList your saved templates
GET/v1/templates/saved/:idGet a specific template
PUT/v1/templates/saved/:idUpdate a template
DELETE/v1/templates/saved/:idDelete a template

POST /v1/templates/saved

Save a new custom template. Templates use Mustache syntax for data binding.

HeaderRequiredDescription
AuthorizationYesBearer gk_your_api_key
Content-TypeYesapplication/json
{
"name": "my-invoice",
"type": "invoice",
"description": "Custom invoice template with company branding",
"html": "<html><head><style>body { font-family: 'Inter', sans-serif; }</style></head><body><h1>Invoice #{{invoice_number}}</h1><p>Client: {{client.name}}</p></body></html>",
"schema": {
"fields": [
{ "name": "invoice_number", "type": "string", "required": true },
{ "name": "client.name", "type": "string", "required": true }
]
},
"style": "stripe-clean",
"isDefault": false
}
ParameterTypeRequiredDescription
namestringYesTemplate name (1-255 characters)
htmlstringYesMustache HTML template
typestringNoDocument type: invoice, quote, report, certificate, letter, receipt, contract, custom
descriptionstringNoHuman-readable description (max 1000 characters)
schemaobjectNoField schema for validation and documentation
schema.fieldsarrayNoArray of field definitions
schema.fields[].namestringYesField name (supports dot notation for nested fields)
schema.fields[].typestringYesField type: string, number, boolean, array, object
schema.fields[].requiredbooleanNoWhether the field is required
stylestringNoStyle preset: stripe-clean, professional, minimal, bold, classic, corporate, modern, vibrant
isDefaultbooleanNoSet as default template for this type (default: false)
{
"success": true,
"template": {
"id": "550e8400-e29b-41d4-a716-446655440000",
"name": "my-invoice",
"type": "invoice",
"description": "Custom invoice template with company branding",
"html": "<html>...</html>",
"schema": {
"fields": [
{ "name": "invoice_number", "type": "string", "required": true },
{ "name": "client.name", "type": "string", "required": true }
]
},
"style": "stripe-clean",
"isDefault": false,
"version": 1,
"createdAt": "2026-01-28T12:00:00.000Z",
"updatedAt": "2026-01-28T12:00:00.000Z"
}
}
Terminal window
curl -X POST https://api.glyph.you/v1/templates/saved \
-H "Authorization: Bearer gk_your_api_key" \
-H "Content-Type: application/json" \
-d '{
"name": "my-invoice",
"type": "invoice",
"description": "Custom invoice with company branding",
"html": "<html><head><style>body { font-family: Inter, sans-serif; padding: 40px; } h1 { color: #1a1a1a; } .amount { font-size: 24px; font-weight: bold; }</style></head><body><h1>Invoice #{{invoice_number}}</h1><p>Client: {{client.name}}</p><p>Email: {{client.email}}</p><div class=\"amount\">Total: ${{total}}</div></body></html>",
"schema": {
"fields": [
{ "name": "invoice_number", "type": "string", "required": true },
{ "name": "client.name", "type": "string", "required": true },
{ "name": "client.email", "type": "string" },
{ "name": "total", "type": "number", "required": true }
]
},
"style": "stripe-clean"
}'
const response = await fetch('https://api.glyph.you/v1/templates/saved', {
method: 'POST',
headers: {
'Authorization': 'Bearer gk_your_api_key',
'Content-Type': 'application/json',
},
body: JSON.stringify({
name: 'my-invoice',
type: 'invoice',
description: 'Custom invoice with company branding',
html: `
<html>
<head>
<style>
body { font-family: 'Inter', sans-serif; padding: 40px; }
h1 { color: #1a1a1a; }
.amount { font-size: 24px; font-weight: bold; }
</style>
</head>
<body>
<h1>Invoice #{{invoice_number}}</h1>
<p>Client: {{client.name}}</p>
<p>Email: {{client.email}}</p>
<div class="amount">Total: \${{total}}</div>
</body>
</html>
`,
schema: {
fields: [
{ name: 'invoice_number', type: 'string', required: true },
{ name: 'client.name', type: 'string', required: true },
{ name: 'client.email', type: 'string' },
{ name: 'total', type: 'number', required: true },
],
},
style: 'stripe-clean',
}),
});
const { template } = await response.json();
console.log(`Template saved with ID: ${template.id}`);
import requests
response = requests.post(
'https://api.glyph.you/v1/templates/saved',
headers={
'Authorization': 'Bearer gk_your_api_key',
'Content-Type': 'application/json',
},
json={
'name': 'my-invoice',
'type': 'invoice',
'description': 'Custom invoice with company branding',
'html': '''
<html>
<head>
<style>
body { font-family: 'Inter', sans-serif; padding: 40px; }
h1 { color: #1a1a1a; }
.amount { font-size: 24px; font-weight: bold; }
</style>
</head>
<body>
<h1>Invoice #{{invoice_number}}</h1>
<p>Client: {{client.name}}</p>
<p>Email: {{client.email}}</p>
<div class="amount">Total: ${{total}}</div>
</body>
</html>
''',
'schema': {
'fields': [
{'name': 'invoice_number', 'type': 'string', 'required': True},
{'name': 'client.name', 'type': 'string', 'required': True},
{'name': 'client.email', 'type': 'string'},
{'name': 'total', 'type': 'number', 'required': True},
]
},
'style': 'stripe-clean',
}
)
template = response.json()['template']
print(f"Template saved with ID: {template['id']}")

GET /v1/templates/saved

List all templates saved under your API key.

HeaderRequiredDescription
AuthorizationYesBearer gk_your_api_key
ParameterTypeDefaultDescription
typestringFilter by document type
limitnumber50Results per page (1-100)
offsetnumber0Pagination offset
{
"success": true,
"templates": [
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"name": "my-invoice",
"type": "invoice",
"description": "Custom invoice with company branding",
"style": "stripe-clean",
"isDefault": false,
"version": 1,
"createdAt": "2026-01-28T12:00:00.000Z",
"updatedAt": "2026-01-28T12:00:00.000Z"
}
],
"total": 1,
"limit": 50,
"offset": 0
}
Terminal window
# List all templates
curl -X GET https://api.glyph.you/v1/templates/saved \
-H "Authorization: Bearer gk_your_api_key"
# Filter by type
curl -X GET "https://api.glyph.you/v1/templates/saved?type=invoice" \
-H "Authorization: Bearer gk_your_api_key"
# Paginate results
curl -X GET "https://api.glyph.you/v1/templates/saved?limit=10&offset=20" \
-H "Authorization: Bearer gk_your_api_key"
const response = await fetch('https://api.glyph.you/v1/templates/saved?type=invoice', {
headers: {
'Authorization': 'Bearer gk_your_api_key',
},
});
const { templates, total } = await response.json();
console.log(`Found ${total} invoice templates`);
for (const template of templates) {
console.log(`- ${template.name} (v${template.version})`);
}

GET /v1/templates/saved/:id

Get a specific template by ID, including full HTML and schema.

HeaderRequiredDescription
AuthorizationYesBearer gk_your_api_key
ParameterTypeDescription
idstring (UUID)Template ID
{
"success": true,
"template": {
"id": "550e8400-e29b-41d4-a716-446655440000",
"name": "my-invoice",
"type": "invoice",
"description": "Custom invoice with company branding",
"html": "<html>...</html>",
"schema": {
"fields": [
{ "name": "invoice_number", "type": "string", "required": true }
]
},
"style": "stripe-clean",
"isDefault": false,
"version": 1,
"createdAt": "2026-01-28T12:00:00.000Z",
"updatedAt": "2026-01-28T12:00:00.000Z"
}
}
Terminal window
curl -X GET https://api.glyph.you/v1/templates/saved/550e8400-e29b-41d4-a716-446655440000 \
-H "Authorization: Bearer gk_your_api_key"
const templateId = '550e8400-e29b-41d4-a716-446655440000';
const response = await fetch(`https://api.glyph.you/v1/templates/saved/${templateId}`, {
headers: {
'Authorization': 'Bearer gk_your_api_key',
},
});
const { template } = await response.json();
console.log(`Template: ${template.name}`);
console.log(`HTML length: ${template.html.length} characters`);

PUT /v1/templates/saved/:id

Update an existing template. Only include the fields you want to change.

HeaderRequiredDescription
AuthorizationYesBearer gk_your_api_key
Content-TypeYesapplication/json
ParameterTypeDescription
idstring (UUID)Template ID

All fields are optional. Include only what you want to update.

{
"name": "my-invoice-v2",
"description": "Updated invoice template",
"html": "<html>...</html>",
"style": "professional"
}
{
"success": true,
"template": {
"id": "550e8400-e29b-41d4-a716-446655440000",
"name": "my-invoice-v2",
"type": "invoice",
"description": "Updated invoice template",
"html": "<html>...</html>",
"schema": { ... },
"style": "professional",
"isDefault": false,
"version": 2,
"createdAt": "2026-01-28T12:00:00.000Z",
"updatedAt": "2026-01-28T14:30:00.000Z"
}
}
Terminal window
curl -X PUT https://api.glyph.you/v1/templates/saved/550e8400-e29b-41d4-a716-446655440000 \
-H "Authorization: Bearer gk_your_api_key" \
-H "Content-Type: application/json" \
-d '{
"description": "Updated invoice template with new footer",
"style": "professional"
}'
const templateId = '550e8400-e29b-41d4-a716-446655440000';
const response = await fetch(`https://api.glyph.you/v1/templates/saved/${templateId}`, {
method: 'PUT',
headers: {
'Authorization': 'Bearer gk_your_api_key',
'Content-Type': 'application/json',
},
body: JSON.stringify({
description: 'Updated invoice template with new footer',
style: 'professional',
}),
});
const { template } = await response.json();
console.log(`Updated to version ${template.version}`);

DELETE /v1/templates/saved/:id

Delete a saved template. This action is permanent.

HeaderRequiredDescription
AuthorizationYesBearer gk_your_api_key
ParameterTypeDescription
idstring (UUID)Template ID
{
"success": true,
"deleted": "550e8400-e29b-41d4-a716-446655440000"
}
Terminal window
curl -X DELETE https://api.glyph.you/v1/templates/saved/550e8400-e29b-41d4-a716-446655440000 \
-H "Authorization: Bearer gk_your_api_key"
const templateId = '550e8400-e29b-41d4-a716-446655440000';
const response = await fetch(`https://api.glyph.you/v1/templates/saved/${templateId}`, {
method: 'DELETE',
headers: {
'Authorization': 'Bearer gk_your_api_key',
},
});
const { deleted } = await response.json();
console.log(`Deleted template: ${deleted}`);

Once saved, use your custom template with the /v1/create endpoint by passing the template ID:

Terminal window
curl -X POST https://api.glyph.you/v1/create \
-H "Authorization: Bearer gk_your_api_key" \
-H "Content-Type: application/json" \
-d '{
"templateId": "550e8400-e29b-41d4-a716-446655440000",
"data": {
"invoice_number": "INV-2026-001",
"client": {
"name": "Acme Corp",
"email": "billing@acme.com"
},
"total": 4500
}
}'

Setting isDefault: true marks a template as the default for its document type. When you have a default template:

  • Only one template per type can be default
  • Setting a new default automatically unsets the previous one
  • Useful for automated workflows that don’t specify a template ID
// Set as default invoice template
await fetch('https://api.glyph.you/v1/templates/saved', {
method: 'POST',
headers: {
'Authorization': 'Bearer gk_your_api_key',
'Content-Type': 'application/json',
},
body: JSON.stringify({
name: 'company-invoice',
type: 'invoice',
html: '...',
isDefault: true, // This becomes the default invoice template
}),
});

Custom templates use Mustache for data binding. Here are the most common patterns:

<h1>Invoice #{{invoice_number}}</h1>
<p>Client: {{client.name}}</p>
{{#paid}}
<span class="badge">PAID</span>
{{/paid}}
{{^paid}}
<span class="badge warning">UNPAID</span>
{{/paid}}
<table>
{{#items}}
<tr>
<td>{{description}}</td>
<td>{{quantity}}</td>
<td>${{total}}</td>
</tr>
{{/items}}
</table>
<address>
{{client.address.street}}<br>
{{client.address.city}}, {{client.address.state}} {{client.address.zip}}
</address>

{
"error": "Validation failed",
"code": "VALIDATION_ERROR",
"details": [
{ "path": ["name"], "message": "Template name is required" }
]
}
{
"error": "Template persistence requires a registered API key. Please sign up to save templates.",
"code": "DEMO_TIER_LIMITATION"
}
{
"error": "Template not found",
"code": "TEMPLATE_NOT_FOUND"
}
{
"error": "Database is not configured",
"code": "DATABASE_NOT_CONFIGURED"
}