Proposals API
All authenticated endpoints require a valid Cloudflare Access JWT. Public endpoints use a share token instead.
Base paths:
- Authenticated:
/api/proposals - Public (no auth):
/api/proposals/public
Templates
Section titled “Templates”GET /api/proposals/templates
Section titled “GET /api/proposals/templates”List active proposal templates.
Query params — category (optional filter)
Response { "templates": ProposalTemplate[] }
POST /api/proposals/templates
Section titled “POST /api/proposals/templates”Create a new template.
Body
| Field | Type | Description |
|---|---|---|
name | string | Template name |
description | string? | Optional description |
category | string? | Template category |
content_rich | string? | Rich content JSON |
brand_config | string? | Brand configuration JSON |
variables_schema | string? | Variable definitions JSON |
speaker_notes | string? | Speaker notes JSON |
contract_template_id | string? | Linked contract template |
Response { "id": "string" }
GET /api/proposals/templates/:id
Section titled “GET /api/proposals/templates/:id”Get a single template including content and configuration.
PATCH /api/proposals/templates/:id
Section titled “PATCH /api/proposals/templates/:id”Update template metadata and/or content.
Body — same fields as POST (all optional)
Response { "ok": true }
DELETE /api/proposals/templates/:id
Section titled “DELETE /api/proposals/templates/:id”Soft-deletes the template (is_active = 0). Existing proposals are not affected.
Response { "ok": true }
Proposals
Section titled “Proposals”GET /api/proposals
Section titled “GET /api/proposals”List proposals. Scoped by access level.
Query params
status— filter by statusdeal_id— filter by dealowner_id— filter by ownercompany_id— filter by company
Response { "proposals": Proposal[] }
POST /api/proposals
Section titled “POST /api/proposals”Create a new proposal. If template_id is provided, content is cloned from the template with variables resolved.
Body
| Field | Type | Description |
|---|---|---|
title | string | Proposal title |
template_id | string? | Template to create from |
deal_id | string? | Deal to pull data from |
company_id | string? | Client company |
contact_id | string? | Primary contact |
valid_until | string? | Expiry date (ISO) |
currency | string? | Currency code (default: AUD) |
Response { "id": "string", "page_id": "string" }
GET /api/proposals/:id
Section titled “GET /api/proposals/:id”Get full proposal detail including joined company, contact, deal, and owner names.
Response { "proposal": Proposal }
PATCH /api/proposals/:id
Section titled “PATCH /api/proposals/:id”Update proposal metadata.
Body — title, deal_id, company_id, contact_id, valid_until, currency, speaker_notes, brand_config (all optional)
Response { "ok": true }
DELETE /api/proposals/:id
Section titled “DELETE /api/proposals/:id”Delete a proposal and its associated page.
Response { "ok": true }
POST /api/proposals/:id/duplicate
Section titled “POST /api/proposals/:id/duplicate”Create a copy of the proposal including page content and pricing sections. The copy is created in “draft” status.
Response { "proposal": { "id": "string" } }
Workflow
Section titled “Workflow”POST /api/proposals/:id/submit-review
Section titled “POST /api/proposals/:id/submit-review”Submit the proposal for internal approval. Transitions status from draft to review.
Response { "ok": true }
POST /api/proposals/:id/approve
Section titled “POST /api/proposals/:id/approve”Approve the proposal. When all approvers have approved, status transitions to approved.
Body { "comments": "optional string" }
Response { "ok": true }
POST /api/proposals/:id/request-changes
Section titled “POST /api/proposals/:id/request-changes”Request changes on a proposal under review. Transitions status to changes_requested.
Body { "comments": "required string" }
Response { "ok": true }
POST /api/proposals/:id/send
Section titled “POST /api/proposals/:id/send”Mark the proposal as sent to the client. Transitions status to sent.
Response { "ok": true }
Sharing & Analytics
Section titled “Sharing & Analytics”POST /api/proposals/:id/share
Section titled “POST /api/proposals/:id/share”Configure sharing settings.
Body
{ "share_enabled": true, "share_password": "optional string"}Response { "share_token": "string" }
GET /api/proposals/:id/analytics
Section titled “GET /api/proposals/:id/analytics”Get view analytics for the proposal.
Response { "analytics": ProposalAnalytics[] }
Pricing
Section titled “Pricing”GET /api/proposals/:id/pricing
Section titled “GET /api/proposals/:id/pricing”Get all pricing sections and their line items.
Response { "sections": ProposalPricingSection[] }
POST /api/proposals/:id/pricing
Section titled “POST /api/proposals/:id/pricing”Create or replace pricing sections and items.
Body
{ "sections": [ { "title": "Development", "position": 0, "items": [ { "description": "Frontend build", "quantity": 1, "unit_price": 5000, "amount": 5000, "notes": "React SPA" } ] } ]}Response { "ok": true }
DELETE /api/proposals/:id/pricing/:sectionId
Section titled “DELETE /api/proposals/:id/pricing/:sectionId”Delete a pricing section and its items.
Response { "ok": true }
Public Endpoints
Section titled “Public Endpoints”No authentication required. These endpoints use a share token.
GET /api/proposals/public/:shareToken
Section titled “GET /api/proposals/public/:shareToken”Returns the proposal content for the public viewer. If the proposal is password-protected and no valid session exists, returns { "password_required": true }.
POST /api/proposals/public/:shareToken/verify
Section titled “POST /api/proposals/public/:shareToken/verify”Verify the password for a protected proposal.
Body { "password": "string" }
Response { "ok": true } or { "error": "Invalid password" }
POST /api/proposals/public/:shareToken/track
Section titled “POST /api/proposals/public/:shareToken/track”Track a view event for analytics.
Body
{ "session_id": "string", "slide_index": 0, "duration_delta": 5}Response { "ok": true }
POST /api/proposals/public/:shareToken/accept
Section titled “POST /api/proposals/public/:shareToken/accept”Accept the proposal.
Body
{ "name": "string", "email": "string"}Response { "success": true }
On success: transitions proposal to accepted, triggers onboarding automation (project, budget, invoice, contract).
POST /api/proposals/public/:shareToken/decline
Section titled “POST /api/proposals/public/:shareToken/decline”Decline the proposal.
Body
{ "name": "string", "email": "string", "reason": "optional string"}Response { "success": true }
Proposal
Section titled “Proposal”interface Proposal { id: string; title: string; deal_id: string | null; company_id: string | null; contact_id: string | null; template_id: string | null; page_id: string; status: ProposalStatus; version: number; currency: string; total_amount: number | null; valid_until: string | null; speaker_notes: string; brand_config: string; share_token: string | null; share_password: string | null; share_enabled: number; owner_id: string; sent_at: string | null; viewed_at: string | null; accepted_at: string | null; declined_at: string | null; decline_reason: string | null; contract_id: string | null; project_id: string | null; created_by: string; created_at: string; updated_at: string;}ProposalStatus
Section titled “ProposalStatus”draft | review | changes_requested | approved | sent | viewed | accepted | declined | expiredProposalPricingSection
Section titled “ProposalPricingSection”interface ProposalPricingSection { id: string; proposal_id: string; title: string; position: number; items: ProposalPricingItem[];}ProposalPricingItem
Section titled “ProposalPricingItem”interface ProposalPricingItem { id: string; section_id: string; description: string; service_type_id: string | null; quantity: number; unit_price: number; amount: number; notes: string | null; position: number;}