Skip to main content

Discovery & Quotes API – cURL Commands for Postman

This document provides a complete reference for the Discovery and Quotes module API endpoints, including cURL commands ready to import or use in Postman.


Module Overview

Discovery Module (apps/backend/src/discovery/)

Public-facing kiosk/catalog interface for product discovery and quote requests.

ControllerBase PathDescription
DiscoveryController/discoveryConfig, products catalog, create quote (kiosk)

Dependencies: DatabaseModule, QuotesModule

ServiceDescription
DiscoveryServiceOrchestrates config, product catalog, and kiosk quote flow
DiscoveryRepositoryKysely queries for products and discovery config

Quotes Module (apps/backend/src/quotes/)

Staff-facing quote management, PDF generation, and conversion to sales.

ControllerBase PathDescription
QuotesController/quotesCRUD, PDF, public links, convert to sale

Dependencies: DatabaseModule, PdfModule, SalesModule

ServiceDescription
QuotesServiceCRUD, PDF generation, public links, convert to sale
QuotesRepositoryKysely queries for quotes
PublicDocumentLinkServiceGenerates signed URLs for quote PDFs

Authentication

  • Discovery endpoints: Currently protected by global AuthGuard. Send Firebase ID token when required:
    • Header: Authorization: Bearer <firebase-id-token>
    • Cookie: flowpos-id-token (Firebase ID token)
  • Quotes endpoints: All require Firebase authentication (same as above).
  • Discovery POST /discovery/quotes: Intended for kiosk (public). If @IsPublic() is added, no auth needed; otherwise include token.

Base URL

  • Local: http://localhost:4000
  • Staging/Production: Replace with your deployed backend URL (e.g. https://flowpos-backend-xxx.run.app)

Environment Variables (Postman)

Use these for easy replacement:

VariableExampleDescription
BASE_URLhttp://localhost:4000API base URL
TOKENeyJhbGci...Firebase ID token
locationIduuid-hereLocation UUID
businessIduuid-hereBusiness UUID
productIduuid-hereProduct UUID
quoteIduuid-hereQuote UUID
userIduuid-hereUser UUID (createdBy)

Discovery Endpoints

1. Get Discovery Config

Returns discovery config for a location (enabled, low stock threshold, polling interval).

curl -X GET '{{BASE_URL}}/discovery/config?locationId={{locationId}}' \
-H 'Authorization: Bearer {{TOKEN}}'

Query params:

ParamTypeRequiredDescription
locationIdUUIDYesLocation UUID

Response (200):

{
"enabled": true,
"lowStockThreshold": 5,
"pollingIntervalSeconds": 60
}

2. List Products (Discovery Catalog)

Paginated product list with availability for the discovery catalog.

curl -X GET '{{BASE_URL}}/discovery/products?locationId={{locationId}}&page=1&limit=24' \
-H 'Authorization: Bearer {{TOKEN}}'

Query params:

ParamTypeRequiredDefaultDescription
locationIdUUIDYes-Location UUID
searchstringNo-Search term (product name, etc.)
categoryIdUUIDNo-Filter by category
inStockOnlybooleanNo-Only in-stock products
onSaleOnlybooleanNo-Only products on sale
pagenumberNo1Page number
limitnumberNo24Items per page (max 100)

Example with filters:

curl -X GET '{{BASE_URL}}/discovery/products?locationId={{locationId}}&search=coffee&inStockOnly=true&page=1&limit=12' \
-H 'Authorization: Bearer {{TOKEN}}'

3. Get Product Detail

Product detail with availability matrix for a specific product.

curl -X GET '{{BASE_URL}}/discovery/products/{{productId}}?locationId={{locationId}}' \
-H 'Authorization: Bearer {{TOKEN}}'

Path params: productId (UUID)
Query params: locationId (UUID, required)


4. Create Quote (Kiosk)

Creates a quote from the kiosk/discovery interface. Uses origin: kiosk, status: requested.
Rate limit: 5 per hour per IP (ThrottlerGuard).

curl -X POST '{{BASE_URL}}/discovery/quotes' \
-H 'Content-Type: application/json' \
-d '{
"locationId": "{{locationId}}",
"customerName": "John Doe",
"customerEmail": "john@example.com",
"customerPhone": "+50212345678",
"notes": "Please call before delivery",
"items": [
{
"productId": "{{productId}}",
"productName": "Coffee Beans 1kg",
"quantity": 2,
"locationId": "{{locationId}}",
"unitPrice": 25.99
}
]
}'

Request body:

FieldTypeRequiredDescription
locationIdUUIDYesLocation UUID
customerNamestringYesCustomer name
customerEmailstringNo*At least one of email or phone required
customerPhonestringNo*At least one of email or phone required
notesstringNoAdditional notes
itemsarrayYesAt least one item

Item shape:

FieldTypeRequiredDescription
productIdUUIDYesProduct UUID
productNamestringYesProduct name
quantitynumberYesMin 0.001
locationIdUUIDNoDefaults to top-level locationId
unitPricenumberNoUnit price

Response (201):

{
"id": "quote-uuid"
}

Quotes Endpoints (Staff)

All quotes endpoints require Firebase authentication.

5. Create Quote (Staff)

Creates a quote with origin: staff, status: draft. Includes full metadata (tax, currency, etc.).

curl -X POST '{{BASE_URL}}/quotes' \
-H 'Authorization: Bearer {{TOKEN}}' \
-H 'Content-Type: application/json' \
-d '{
"businessId": "{{businessId}}",
"locationId": "{{locationId}}",
"customerName": "Jane Smith",
"customerEmail": "jane@example.com",
"customerPhone": "+50298765432",
"totalAmount": 150.00,
"totalBaseAmount": 150.00,
"notes": "Rush order",
"items": [
{
"productId": "{{productId}}",
"productName": "Widget A",
"quantity": 3,
"unitPrice": 50.00,
"amount": 150.00,
"locationId": "{{locationId}}"
}
],
"createdBy": "{{userId}}"
}'

Required: businessId, locationId, customerName, items (at least one)
Optional: customerId, customerEmail, customerPhone, taxId, taxName, taxAddress, taxpayerType, locationName, currencyId, currencyCode, exchangeRate, totalAmount, totalBaseAmount, expiresAt, notes, createdBy


6. List Quotes (Paginated)

curl -X GET '{{BASE_URL}}/quotes?businessId={{businessId}}&page=1&size=10&orderBy=createdAt&order=desc' \
-H 'Authorization: Bearer {{TOKEN}}'

Query params:

ParamTypeRequiredDescription
businessIdUUIDNoFilter by business
locationIdUUIDNoFilter by location
statusstringNorequested, draft, sent, accepted, expired, rejected
documentNumberstringNoFilter by document number
createdAtFromISO dateNoDate range start
createdAtToISO dateNoDate range end
search / querystringNoSearch term
pagenumberNoPage (default 1)
sizenumberNoPage size (default 10)
orderBystringNodocumentNumber, customerName, taxId, taxName, locationName, status, createdAt
orderstringNoasc or desc

7. Get Quote by ID

curl -X GET '{{BASE_URL}}/quotes/{{quoteId}}' \
-H 'Authorization: Bearer {{TOKEN}}'

8. Update Quote

curl -X PATCH '{{BASE_URL}}/quotes/{{quoteId}}' \
-H 'Authorization: Bearer {{TOKEN}}' \
-H 'Content-Type: application/json' \
-d '{
"status": "sent",
"customerEmail": "updated@example.com",
"notes": "Updated notes",
"updatedBy": "{{userId}}"
}'

Partial update. All fields optional: customerName, customerId, customerEmail, customerPhone, taxId, taxName, taxAddress, taxpayerType, locationName, currencyId, currencyCode, exchangeRate, totalAmount, totalBaseAmount, expiresAt, notes, status, items, updatedBy.

Quote status values: requested, draft, sent, accepted, expired, rejected


9. Delete Quote

curl -X DELETE '{{BASE_URL}}/quotes/{{quoteId}}' \
-H 'Authorization: Bearer {{TOKEN}}'

10. Generate Quote PDF (Download)

Returns PDF file as attachment.

curl -X GET '{{BASE_URL}}/quotes/{{quoteId}}/pdf' \
-H 'Authorization: Bearer {{TOKEN}}' \
--output quote.pdf

Query params (optional):

ParamTypeDefaultDescription
pageSizestringA4Page size
marginTopnumber20Top margin (0-100)
marginRightnumber20Right margin
marginBottomnumber20Bottom margin
marginLeftnumber20Left margin
printBackgroundbooleantruePrint background graphics
timeoutnumber30000Puppeteer timeout (ms)
templateIdstring-Custom template ID
locationIdstring-Location for template
useLocationTemplatestring-"true" to use location template

11. Get Quote PDF as Base64

Returns JSON with base64-encoded PDF content.

curl -X GET '{{BASE_URL}}/quotes/{{quoteId}}/pdf/content' \
-H 'Authorization: Bearer {{TOKEN}}'

Response (200):

{
"filename": "Quote-001-2025-02-21.pdf",
"content": "JVBERi0xLjQK...",
"mimeType": "application/pdf",
"sizeBytes": 12345
}

12. Get Quote Print Preview (HTML)

Returns HTML for print preview.

curl -X GET '{{BASE_URL}}/quotes/{{quoteId}}/print?templateId={{templateId}}&locationId={{locationId}}&useLocationTemplate=true' \
-H 'Authorization: Bearer {{TOKEN}}'

Generates a signed URL for the quote PDF. Requires PublicDocumentLinkService configuration.

curl -X POST '{{BASE_URL}}/quotes/{{quoteId}}/public-link' \
-H 'Authorization: Bearer {{TOKEN}}' \
-H 'Content-Type: application/json' \
-d '{
"expiresInMinutes": 60
}'

Request body (optional):

FieldTypeDescription
expiresInMinutesnumberLink expiry (default TBD)

Response (201):

{
"url": "https://storage.googleapis.com/...",
"expiresAt": "2025-02-21T18:00:00.000Z",
"objectPath": "communications/..."
}

14. Get Quote Template Data

Returns template variables for document delivery (e.g. email merge fields).

curl -X GET '{{BASE_URL}}/quotes/{{quoteId}}/template-data' \
-H 'Authorization: Bearer {{TOKEN}}'

15. Convert Quote to Sale

Converts an accepted quote to a sale. Quote must have status: accepted and at least one item.

curl -X POST '{{BASE_URL}}/quotes/{{quoteId}}/convert-to-sale' \
-H 'Authorization: Bearer {{TOKEN}}' \
-H 'Content-Type: application/json' \
-d '{
"createdBy": "{{userId}}"
}'

Note: createdBy can come from the authenticated Firebase user; if not in token, pass in body.

Response (201): Returns the created SelectableSale object.


Summary Table

#MethodEndpointAuthDescription
1GET/discovery/configYes*Get discovery config
2GET/discovery/productsYes*List products (catalog)
3GET/discovery/products/:productIdYes*Product detail
4POST/discovery/quotesNo**Create quote (kiosk)
5POST/quotesYesCreate quote (staff)
6GET/quotesYesList quotes
7GET/quotes/:idYesGet quote by ID
8PATCH/quotes/:idYesUpdate quote
9DELETE/quotes/:idYesDelete quote
10GET/quotes/:id/pdfYesDownload quote PDF
11GET/quotes/:id/pdf/contentYesQuote PDF as base64
12GET/quotes/:id/printYesPrint preview (HTML)
13POST/quotes/:id/public-linkYesCreate public PDF link
14GET/quotes/:id/template-dataYesGet template variables
15POST/quotes/:id/convert-to-saleYesConvert accepted quote to sale

* Discovery routes may require auth depending on @IsPublic() configuration.
** POST /discovery/quotes has rate limit (5/hour per IP); intended for public kiosk use.