API Documentation

Getting Started

Quote Plus is a REST API to create and manage quotes simply and securely. Create quotes, share unique links, automatically generate PDFs, and receive responses via webhooks.

Quick Start

  1. Create an account in the dashboard and get your API Key or JWT Token
  2. Make a request to create your first quote
  3. Share the link returned with your client
  4. Receive notifications when the quote is accepted or rejected

Base URL

https://quote.plus/api

API Response Format

When creating a quote, you'll receive a response with the quote details and URLs:

{
  "id": "q_01JABCD123",
  "status": "PENDING",
  "publicUrl": "https://quote.plus/q/abc123xyz",
  "pdfUrl": "http://localhost:9000/quotes/pdfs/quote-abc123xyz.pdf"
}

publicUrl: Link público para visualizar a quote

pdfUrl: Link para download do PDF (opcional - só aparece se PDF gerado com sucesso)

Authentication

Option 1: JWT Token (Recommended)

Use JWT tokens from Supabase Auth for more secure authentication.

Authorization: Bearer <your-jwt-token>

Get the token via POST /api/auth/service-token

Option 2: API Key

Use your API Key for authentication (better for automated scripts).

Authorization: Bearer qp_your_api_key_here

Rate Limit: 100 requests per 15 minutes per user

Examples & Use Cases

Basic Use Cases

Basic Quote

Simple creation of a public quote

{
  "customer": {
    "name": "John Doe",
    "email": "[email protected]"
  },
  "items": [
    {
      "description": "Website Development",
      "qty": 1,
      "unitPrice": 5000
    }
  ],
  "currency": "USD",
  "visibility": "PUBLIC"
}

Quote with Total Provided

When you already know the total and don't need to calculate per item

{
  "customer": {
    "name": "Jane Smith",
    "email": "[email protected]"
  },
  "items": [
    {
      "description": "Complete Consulting Service",
      "qty": 1
    }
  ],
  "currency": "USD",
  "total": 2500,
  "description": "Complete technical consulting service",
  "visibility": "PUBLIC"
}

Private Quote with Password

Password-protected quote

{
  "customer": {
    "name": "Bob Johnson",
    "email": "[email protected]"
  },
  "items": [
    {
      "description": "Confidential Project",
      "qty": 1,
      "unitPrice": 10000
    }
  ],
  "currency": "USD",
  "visibility": "PRIVATE",
  "password": "secure123",
  "expiresAt": "2025-12-31T23:59:59Z"
}

Advanced Use Cases

Quote with Discounts and Taxes

Items with percentage discounts and taxes

{
  "customer": {
    "name": "Alice Williams",
    "email": "[email protected]",
    "phone": "+1 555 123-4567"
  },
  "items": [
    {
      "description": "Mobile App Development",
      "qty": 1,
      "unitPrice": 15000,
      "discount": {
        "type": "PERCENT",
        "value": 10
      },
      "taxes": [
        {
          "name": "Sales Tax",
          "type": "PERCENT",
          "value": 5
        }
      ]
    },
    {
      "description": "Annual Hosting",
      "qty": 1,
      "unitPrice": 1200,
      "discount": {
        "type": "FIXED",
        "value": 200
      }
    }
  ],
  "currency": "USD",
  "description": "Complete mobile development project",
  "visibility": "PUBLIC"
}

Quote with Installments

Payment divided into installments with due dates

{
  "customer": {
    "name": "Charlie Brown",
    "email": "[email protected]"
  },
  "items": [
    {
      "description": "Digital Marketing Service",
      "qty": 1,
      "unitPrice": 8000
    }
  ],
  "currency": "USD",
  "total": 8000,
  "paymentTerms": "Net 30 - Payment in 2 installments",
  "installments": [
    {
      "number": 1,
      "amount": 4000,
      "dueDate": "2025-01-15T00:00:00Z",
      "paymentMethod": "WIRE"
    },
    {
      "number": 2,
      "amount": 4000,
      "dueDate": "2025-02-15T00:00:00Z",
      "paymentMethod": "WIRE"
    }
  ],
  "visibility": "PUBLIC"
}

Quote with Attachments and Item Details

Quote with attachment links and items with notes/sub-items

{
  "customer": {
    "name": "ABC Corporation",
    "email": "[email protected]"
  },
  "items": [
    {
      "description": "Website Redesign Package",
      "qty": 1,
      "unitPrice": 12000,
      "notes": "Includes responsive design, SEO optimization, and 3 months of support",
      "subItems": [
        {
          "description": "Homepage Design",
          "qty": 1,
          "notes": "Custom design with animations"
        },
        {
          "description": "Product Pages",
          "qty": 10,
          "notes": "Template-based with customization"
        },
        {
          "description": "Mobile Optimization",
          "qty": 1
        }
      ]
    },
    {
      "description": "Content Management System",
      "qty": 1,
      "unitPrice": 5000,
      "notes": "Custom CMS with user-friendly admin panel"
    }
  ],
  "currency": "USD",
  "description": "Complete website redesign and CMS implementation",
  "attachments": [
    {
      "url": "https://example.com/proposals/website-redesign-proposal.pdf",
      "name": "Detailed Proposal PDF",
      "type": "PDF"
    },
    {
      "url": "https://example.com/mockups/homepage-design.png",
      "name": "Homepage Mockup",
      "type": "IMAGE"
    },
    {
      "url": "https://example.com/docs/technical-specs.pdf",
      "name": "Technical Specifications",
      "type": "PDF"
    }
  ],
  "visibility": "PUBLIC"
}

Complete Quote with All Fields

Complete example using all available features

{
  "customer": {
    "name": "XYZ Company Inc",
    "email": "[email protected]",
    "phone": "+1 555 333-4444"
  },
  "items": [
    {
      "description": "ERP System Development",
      "qty": 1,
      "unitPrice": 50000,
      "notes": "Includes custom modules for inventory, accounting, and HR",
      "subItems": [
        {
          "description": "Inventory Management Module",
          "qty": 1
        },
        {
          "description": "Accounting Module",
          "qty": 1
        },
        {
          "description": "HR Management Module",
          "qty": 1
        }
      ],
      "discount": {
        "type": "PERCENT",
        "value": 15
      },
      "taxes": [
        {
          "name": "Sales Tax",
          "type": "PERCENT",
          "value": 5
        }
      ],
      "metadata": {
        "category": "development",
        "priority": "high"
      }
    },
    {
      "description": "Team Training",
      "qty": 3,
      "unitPrice": 2000,
      "notes": "3-day intensive training program for your team"
    }
  ],
  "currency": "USD",
  "description": "Complete custom ERP system development and implementation project",
  "attachments": [
    {
      "url": "https://example.com/docs/erp-proposal.pdf",
      "name": "ERP Proposal Document",
      "type": "PDF"
    },
    {
      "url": "https://example.com/images/system-architecture.png",
      "name": "System Architecture Diagram",
      "type": "IMAGE"
    }
  ],
  "subtotal": 56000,
  "discountTotal": 7500,
  "taxTotal": 2425,
  "serviceFee": 500,
  "total": 51425,
  "expiresAt": "2025-12-31T23:59:59Z",
  "visibility": "PRIVATE",
  "password": "confidential123",
  "webhookUrl": "https://mysystem.com/webhooks/quote-plus",
  "paymentTerms": "50% on signing, 50% on delivery",
  "installments": [
    {
      "number": 1,
      "amount": 25712.5,
      "dueDate": "2025-01-01T00:00:00Z",
      "paymentMethod": "WIRE"
    },
    {
      "number": 2,
      "amount": 25712.5,
      "dueDate": "2025-06-01T00:00:00Z",
      "paymentMethod": "WIRE"
    }
  ],
  "tags": [
    "urgent",
    "enterprise",
    "development"
  ],
  "metadata": {
    "projectId": "PROJ-2025-001",
    "salesRep": "John Doe"
  },
  "internalReference": "REF-2025-001"
}

Field Reference

PDF Generation & Storage

Automatic PDF Generation

When creating a quote via POST /api/quotes, the system automatically:

  1. Generates a professional PDF with all quote information
  2. Stores the PDF securely
  3. Returns the PDF URL in the response

Response with PDF URL

The response includes a pdfUrl field:

{
  "id": "q_01JABCD123",
  "status": "PENDING",
  "publicUrl": "https://quote.plus/q/abc123xyz",
  "pdfUrl": "http://localhost:9000/quotes/pdfs/quote-abc123xyz.pdf"
}

Note: The pdfUrl field is optional and only included if PDF generation is successful. If PDF generation fails, the quote is still created successfully, but without the pdfUrl field.

PDF Features

The generated PDF includes:

  • Company logo and information (if configured)
  • Quote details (number, date, status, reference)
  • Customer information
  • Complete item list with descriptions, quantities, and prices
  • Item notes and sub-items
  • Discounts and taxes breakdown
  • Payment terms and installments
  • Attachments list
  • Professional formatting ready for printing

Manual PDF Download

You can also download the PDF directly from the quote page or via API:

GET /api/q/:publicId/pdf

Returns the PDF file directly for download.

Example: Using PDF URL

const response = await fetch("https://quote.plus/api/quotes", {
  method: "POST",
  headers: {
    Authorization: "Bearer qp_SUA_API_KEY",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    customer: { name: "John Doe", email: "[email protected]" },
    items: [{ description: "Service", qty: 1, unitPrice: 1000 }],
    currency: "USD",
  }),
});

const quote = await response.json();

// Quote URL for viewing
console.log("View quote:", quote.publicUrl);

// PDF URL for download (if available)
if (quote.pdfUrl) {
  console.log("Download PDF:", quote.pdfUrl);
  // You can use this URL directly or download it
}

Webhooks

Configuration

Configure your webhook in the dashboard to receive notifications when a quote is accepted or rejected.

  • Access the dashboard and go to Settings
  • Configure the webhook URL
  • Choose the events (ACCEPTED, REJECTED)
  • Optionally, configure a Webhook Key for additional security

Headers Sent

Content-Type: application/json
X-QuotePlus-Signature: <HMAC-SHA256-hex>
X-QuotePlus-Event: QUOTE_ACCEPTED | QUOTE_REJECTED
X-QuotePlus-Key: <your-webhook-key> (if configured - for identification only)

Signature Validation (Important!)

Key vs Secret - Understanding the Difference

  • Webhook Secret: Used to calculate the HMAC-SHA256 signature. Copy this from Dashboard → Settings and configure it in your server as an environment variable.
  • Webhook Key: Optional. Sent in the X-QuotePlus-Key header for identification purposes. Do NOT use this for signature validation!

How the Signature is Calculated

QuotePlus calculates the signature using HMAC-SHA256:

// QuotePlus calculates the signature like this:
const signature = crypto
  .createHmac('sha256', WEBHOOK_SECRET)  // Uses your Webhook SECRET
  .update(JSON.stringify(payload))        // The JSON body as a string
  .digest('hex');                         // Returns hexadecimal

Validating in Your Server (Node.js/TypeScript)

import crypto from 'crypto';

// Get your Webhook Secret from Dashboard → Settings
const WEBHOOK_SECRET = process.env.QUOTE_PLUS_WEBHOOK_SECRET;

function validateWebhook(rawBody: string | Buffer, signature: string): boolean {
  // Calculate expected signature using YOUR Webhook Secret
  const expectedSignature = crypto
    .createHmac('sha256', WEBHOOK_SECRET)
    .update(rawBody)  // IMPORTANT: Use the RAW body, not parsed JSON
    .digest('hex');

  // Compare signatures securely (timing-safe)
  try {
    return crypto.timingSafeEqual(
      Buffer.from(signature, 'hex'),
      Buffer.from(expectedSignature, 'hex')
    );
  } catch {
    return false;
  }
}

// Usage in Express/NestJS:
// 1. Configure raw body middleware BEFORE json parsing
// 2. Get signature from header: req.headers['x-quoteplus-signature']
// 3. Validate: validateWebhook(rawBody, signature)

⚠️ Common Mistakes

  • Using Webhook Key instead of Webhook Secret for validation
  • Using parsed JSON body instead of raw body string
  • Different JSON serialization (extra spaces, different key order)
  • Not configuring QUOTE_PLUS_WEBHOOK_SECRET in your .env

Payload Example

{
  "event": "QUOTE_ACCEPTED",
  "quoteId": "q_01JABCD123",
  "publicId": "abc123xyz",
  "status": "ACCEPTED",
  "customer": {
    "name": "John Doe",
    "email": "[email protected]",
    "phone": "+1-555-123-4567"
  },
  "response": {
    "actorName": "John Doe",
    "comment": "Accepted! We can start.",
    "answeredAt": "2025-12-03T15:30:00Z"
  },
  "metadata": {
    "externalQuoteId": "your-system-uuid",
    "customerId": "cust_123",
    "generateWorkOrder": true
  },
  "internalReference": "REF-2025-001",
  "totals": {
    "subtotal": 1000,
    "discountTotal": 0,
    "taxTotal": 80,
    "serviceFee": 0,
    "total": 1080,
    "currency": "USD"
  },
  "originalPayload": {
    "customer": {
      "name": "John Doe"
    },
    "items": [
      {
        "description": "Service",
        "qty": 1,
        "unitPrice": 1000
      }
    ],
    "currency": "USD"
  }
}

Payload Fields

FieldDescription
metadataYour custom metadata from the quote - useful for external system IDs (externalQuoteId, customerId, etc.)
internalReferenceYour internal reference from the quote
totalsCalculated totals: subtotal, discountTotal, taxTotal, serviceFee, total, currency
originalPayloadComplete original quote payload