Proposal Templates API
Manage proposal templates and generate professional proposals from project data.
GET /api/v1/proposal-templates
List all proposal templates for the current tenant.
Response
{
"success": true,
"data": {
"templates": [
{
"id": 1,
"name": "Standard AV Proposal",
"description": "Default template for AV proposals",
"isDefault": true,
"sourceType": "manual",
"createdAt": "2024-01-15T10:30:00Z",
"updatedAt": "2024-01-20T14:15:00Z"
}
],
"totalCount": 1
}
}
GET /api/v1/proposal-templates/{id}
Get a single proposal template including its full HTML content.
Response
{
"success": true,
"data": {
"template": {
"id": 1,
"name": "Standard AV Proposal",
"description": "Default template for AV proposals",
"content": "<h1>{project.name}</h1><p>Prepared for {client.company}...</p>",
"isDefault": true,
"sourceType": "manual",
"createdAt": "2024-01-15T10:30:00Z",
"updatedAt": "2024-01-20T14:15:00Z"
}
}
}
POST /api/v1/proposal-templates
Create a new proposal template.
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
name |
string | Yes | Template name (max 200 chars) |
description |
string | No | Brief description of the template purpose |
content |
string | Yes | HTML template content with variable placeholders (e.g., {project.name}) |
isDefault |
boolean | No | Set as default template for new proposals (default: false) |
Example Request
{
"name": "Conference Room Proposal",
"description": "Template for conference room projects",
"content": "<h1>{project.name}</h1><p>Prepared for {client.company}</p>{#foreach room}<h2>{room.name}</h2>{room.equipment}{/foreach}",
"isDefault": false
}
Response (201 - Created)
Returns the created template with its assigned ID.
PUT /api/v1/proposal-templates/{id}
Update an existing proposal template. All fields are optional - only provided fields are updated.
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
name |
string | No | Template name (max 200 chars) |
description |
string | No | Brief description of the template purpose |
content |
string | No | HTML template content with variable placeholders |
isDefault |
boolean | No | Set as default template (removes default from other templates) |
Example Request
{
"name": "Updated Template Name",
"content": "<h1>{project.name}</h1>...updated content...",
"isDefault": true
}
DELETE /api/v1/proposal-templates/{id}
Delete a proposal template.
Response
{
"success": true,
"data": {
"id": 1,
"name": "Deleted Template",
"deleted": true
}
}
POST /api/v1/proposal-templates/{id}/set-default
Set a template as the default for the tenant. Only one template can be default.
Response
Returns the updated template with isDefault: true.
POST /api/v1/proposal-templates/{id}/preview
Preview a template with sample data. Useful for testing variable substitution without a project.
Response
{
"success": true,
"data": {
"templateId": 1,
"templateName": "Standard AV Proposal",
"html": "<h1>Sample Project Name</h1><p>Prepared for Acme Corporation...</p>",
"usedSampleData": true
}
}
A block-builder template returns a complete themed HTML document — styled cover page, theme CSS, and equipment cards — in html. Add ?format=body to the request URL to get the bare content instead. Classic HTML templates always return body-only and ignore format.
POST /api/v1/proposal-templates/{id}/generate
Generate a proposal by applying the template to a specific project. Substitutes all variables with actual project data.
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
projectId |
integer | Yes | ID of the project to use for variable substitution |
Example Request
{
"projectId": 123
}
Response (200 - Success)
{
"success": true,
"data": {
"templateId": 1,
"templateName": "Standard AV Proposal",
"projectId": 123,
"html": "<h1>Corporate Conference Room</h1><p>Prepared for Acme Corp...</p>",
"variablesUsed": ["project.name", "client.company", "room.name", "room.equipment"]
}
}
A block-builder template returns a complete themed HTML document (cover page + theme styling) in html, and variablesUsed comes back as an empty array. Add ?format=body for the bare themed body. Classic HTML templates return body-only with the substituted variable list.
GET /api/v1/proposal-templates/variables
Get all available template variables with descriptions and sample values.
Response
{
"success": true,
"data": {
"variables": [
{
"path": "project.name",
"displayName": "Project Name",
"description": "The name of the project",
"category": "Project",
"sampleValue": "Sample Project Name",
"syntax": "{project.name}",
"requiresRoomName": false,
"isLoopSyntax": false,
"isLoopVariable": false
},
{
"path": "room.equipment",
"displayName": "Room Equipment Table",
"description": "Equipment table for current room (use in room loop)",
"category": "Room",
"sampleValue": "[Equipment Table]",
"syntax": "{room.equipment}",
"requiresRoomName": false,
"isLoopSyntax": false,
"isLoopVariable": true
}
],
"byCategory": {
"Project": [...],
"Client": [...],
"Room": [...],
"Date": [...]
},
"totalCount": 70
}
}
Variable Categories
| Category | Description | Example Variables |
|---|---|---|
Project |
Project-level information and totals | {project.name}, {project.total}, {project.jobNumber} |
Client |
Client/customer information | {client.company}, {client.contact}, {client.email} |
Room |
Room-level data (use inside loops) | {room.name}, {room.equipment}, {room.total} |
Date |
Date formatting | {date.today}, {date.year} |
All Rooms |
Aggregated data across all rooms | {all.equipment}, {all.summary} |
Room Loop Syntax
Use {#foreach room}...{/foreach} to iterate over rooms:
<h1>{project.name}</h1>
<p>Prepared for {client.company}</p>
{#foreach room}
<h2>{room.name}</h2>
<p>Room {room.index} of {room.quantity}</p>
{room.equipment}
{room.pagebreak}
{/foreach}
<h2>Project Total: {project.totalWithTax}</h2>
Use {room.equipment:Description,Make,Model,Qty,Extended} to specify which columns appear in equipment tables. Available columns: Description, Make, Model, Notes, Qty, Rate, Extended.
Use {#foreach addalt}...{/foreach} to iterate over Add/Alt rooms separately from regular rooms.
