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.
| Controller | Base Path | Description |
|---|---|---|
| DiscoveryController | /discovery | Config, products catalog, create quote (kiosk) |
Dependencies: DatabaseModule, QuotesModule
| Service | Description |
|---|---|
| DiscoveryService | Orchestrates config, product catalog, and kiosk quote flow |
| DiscoveryRepository | Kysely queries for products and discovery config |
Quotes Module (apps/backend/src/quotes/)
Staff-facing quote management, PDF generation, and conversion to sales.
| Controller | Base Path | Description |
|---|---|---|
| QuotesController | /quotes | CRUD, PDF, public links, convert to sale |
Dependencies: DatabaseModule, PdfModule, SalesModule
| Service | Description |
|---|---|
| QuotesService | CRUD, PDF generation, public links, convert to sale |
| QuotesRepository | Kysely queries for quotes |
| PublicDocumentLinkService | Generates 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)
- Header:
- 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:
| Variable | Example | Description |
|---|---|---|
BASE_URL | http://localhost:4000 | API base URL |
TOKEN | eyJhbGci... | Firebase ID token |
locationId | uuid-here | Location UUID |
businessId | uuid-here | Business UUID |
productId | uuid-here | Product UUID |
quoteId | uuid-here | Quote UUID |
userId | uuid-here | User 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:
| Param | Type | Required | Description |
|---|---|---|---|
locationId | UUID | Yes | Location 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:
| Param | Type | Required | Default | Description |
|---|---|---|---|---|
locationId | UUID | Yes | - | Location UUID |
search | string | No | - | Search term (product name, etc.) |
categoryId | UUID | No | - | Filter by category |
inStockOnly | boolean | No | - | Only in-stock products |
onSaleOnly | boolean | No | - | Only products on sale |
page | number | No | 1 | Page number |
limit | number | No | 24 | Items 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:
| Field | Type | Required | Description |
|---|---|---|---|
locationId | UUID | Yes | Location UUID |
customerName | string | Yes | Customer name |
customerEmail | string | No* | At least one of email or phone required |
customerPhone | string | No* | At least one of email or phone required |
notes | string | No | Additional notes |
items | array | Yes | At least one item |
Item shape:
| Field | Type | Required | Description |
|---|---|---|---|
productId | UUID | Yes | Product UUID |
productName | string | Yes | Product name |
quantity | number | Yes | Min 0.001 |
locationId | UUID | No | Defaults to top-level locationId |
unitPrice | number | No | Unit 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:
| Param | Type | Required | Description |
|---|---|---|---|
businessId | UUID | No | Filter by business |
locationId | UUID | No | Filter by location |
status | string | No | requested, draft, sent, accepted, expired, rejected |
documentNumber | string | No | Filter by document number |
createdAtFrom | ISO date | No | Date range start |
createdAtTo | ISO date | No | Date range end |
search / query | string | No | Search term |
page | number | No | Page (default 1) |
size | number | No | Page size (default 10) |
orderBy | string | No | documentNumber, customerName, taxId, taxName, locationName, status, createdAt |
order | string | No | asc 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):
| Param | Type | Default | Description |
|---|---|---|---|
pageSize | string | A4 | Page size |
marginTop | number | 20 | Top margin (0-100) |
marginRight | number | 20 | Right margin |
marginBottom | number | 20 | Bottom margin |
marginLeft | number | 20 | Left margin |
printBackground | boolean | true | Print background graphics |
timeout | number | 30000 | Puppeteer timeout (ms) |
templateId | string | - | Custom template ID |
locationId | string | - | Location for template |
useLocationTemplate | string | - | "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}}'
13. Create Public Download Link for Quote PDF
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):
| Field | Type | Description |
|---|---|---|
expiresInMinutes | number | Link 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
| # | Method | Endpoint | Auth | Description |
|---|---|---|---|---|
| 1 | GET | /discovery/config | Yes* | Get discovery config |
| 2 | GET | /discovery/products | Yes* | List products (catalog) |
| 3 | GET | /discovery/products/:productId | Yes* | Product detail |
| 4 | POST | /discovery/quotes | No** | Create quote (kiosk) |
| 5 | POST | /quotes | Yes | Create quote (staff) |
| 6 | GET | /quotes | Yes | List quotes |
| 7 | GET | /quotes/:id | Yes | Get quote by ID |
| 8 | PATCH | /quotes/:id | Yes | Update quote |
| 9 | DELETE | /quotes/:id | Yes | Delete quote |
| 10 | GET | /quotes/:id/pdf | Yes | Download quote PDF |
| 11 | GET | /quotes/:id/pdf/content | Yes | Quote PDF as base64 |
| 12 | GET | /quotes/:id/print | Yes | Print preview (HTML) |
| 13 | POST | /quotes/:id/public-link | Yes | Create public PDF link |
| 14 | GET | /quotes/:id/template-data | Yes | Get template variables |
| 15 | POST | /quotes/:id/convert-to-sale | Yes | Convert 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.