π Cart API
Add detailing services and products to the Pops Personal Touch cart from any external page.
Overview
Three integration methods are supported. All write to the shared browser cart used by /services, /products, and /estimate.
- URL query params β redirect users to the site with items pre-added.
- postMessage β from an embedded iframe or opener window.
- Global helper β
window.PopsCartwhen embedded on the same origin.
π§ͺ Try it live
Pick services and products, then see the resulting cart state β computed by /api/cart and, if you want, applied to your real browser cart.
Services
Products
Preview URL
https://pops-personal-touch.lovable.app/?addService=auto-detailing&addProduct=foam-shampoo%3A2Equivalent curl (GET /api/cart)
curl -s "https://pops-personal-touch.lovable.app/api/cart?addService=auto-detailing&addProduct=foam-shampoo%3A2" | jqEquivalent curl (POST /api/cart)
curl -X POST "https://pops-personal-touch.lovable.app/api/cart" \
-H "Content-Type: application/json" \
-d '{ "services": [ "auto-detailing" ], "products": [ { "id": "foam-shampoo", "qty": 2 } ] }'Computed cart
{
"services": [
{
"id": "auto-detailing",
"kind": "service",
"title": "Auto Detailing",
"icon": "π",
"qty": 1,
"unitPrice": 199,
"lineTotal": 199,
"meta": {
"durationMin": 60,
"rate": 199
}
}
],
"products": [
{
"id": "foam-shampoo",
"kind": "product",
"title": "pH-Neutral Foam Car Shampoo (1 gal)",
"icon": "π§΄",
"qty": 2,
"unitPrice": 29.99,
"lineTotal": 59.98
}
],
"subtotal": 258.98,
"tax": 15.54,
"taxRate": 0.06,
"total": 274.52,
"itemCount": 3,
"unknownIds": []
}1. URL Query Parameters
Link users directly to any page with addService and/or addProduct. Multiple IDs are comma-separated. Product quantity uses id:qty.
Parameters
| Name | Type | Description |
|---|---|---|
addService | string | Comma-separated service IDs (e.g. auto-detailing,interior-detail) |
addProduct | string | Comma-separated id:qty pairs (e.g. foam-shampoo:2,tire-shine:1). Qty defaults to 1. |
Clickable Examples
Click any link below to open the site with those items pre-added to your cart.
- β Adds Auto Detailing and opens the home page.
- β Adds three services and lands on the Services page.
- β Adds 2 bottles of Foam Car Shampoo and opens the Detail Shop.
- β Adds multiple products with quantities.
- β Mixed: services + products in one URL.
- https://pops-personal-touch.lovable.app/estimate?addService=auto-detailing&addProduct=foam-shampoo:1β Pre-fills the cart and jumps straight to Book Appointment.
Raw URL Template
https://pops-personal-touch.lovable.app/<route>?addService=<id,id,...>&addProduct=<id:qty,id:qty,...>HTML Link Snippet
<a href="https://pops-personal-touch.lovable.app/?addService=auto-detailing&addProduct=foam-shampoo:2">
Add to Pops Cart
</a>curl β verify each URL scenario
The URL params live on the client (they mutate localStorage), but you can hit GET /api/cart with the same params to see exactly what those items would cost:
curl -s "https://pops-personal-touch.lovable.app/api/cart?addService=auto-detailing" | jq
curl -s "https://pops-personal-touch.lovable.app/api/cart?addService=auto-detailing,interior-detail,exterior-detail" | jq
curl -s "https://pops-personal-touch.lovable.app/api/cart?addProduct=foam-shampoo:2" | jq
curl -s "https://pops-personal-touch.lovable.app/api/cart?addProduct=foam-shampoo:2,tire-shine:1" | jq
curl -s "https://pops-personal-touch.lovable.app/api/cart?addService=auto-detailing,interior-detail&addProduct=foam-shampoo:2,tire-shine:1" | jq1b. Remove & Clear via URL
Same URL-param mechanism, but for tearing the cart back down β perfect for testing empty-to-filled flows.
Parameters
| Name | Type | Description |
|---|---|---|
removeService | string | Comma-separated service IDs to remove from the live cart. |
removeProduct | string | Comma-separated product IDs to remove (any qty for that ID is dropped). |
clearCart | 1 / true | Empty the entire cart (services + products). |
Order in a single URL: clearCart β remove* β add*. So you can reset & seed in one hop.
Clickable Examples
- β Empties the cart entirely.
- β Removes two services from the live cart.
- β Removes those product IDs regardless of qty.
- β Reset then seed: perfect for emptyβfilled test flows.
Raw URL Templates
# Clear entire cart
https://pops-personal-touch.lovable.app/<route>?clearCart=1
# Remove specific IDs
https://pops-personal-touch.lovable.app/<route>?removeService=<id,id,...>&removeProduct=<id,id,...>
# Reset & seed in one URL
https://pops-personal-touch.lovable.app/<route>?clearCart=1&addService=<id,...>&addProduct=<id:qty,...>curl β preview a remove / clear against /api/cart
/api/cart is stateless. Pass baseServices / baseProducts to describe the starting cart, then removeService / removeProduct to preview the resulting summary:
# Clear preview β empty summary
curl -s "https://pops-personal-touch.lovable.app/api/cart?clearCart=1" | jq
# Remove preview: start with auto-detailing + foam-shampoo:2, remove auto-detailing
curl -s "https://pops-personal-touch.lovable.app/api/cart?baseServices=auto-detailing,interior-detail&baseProducts=foam-shampoo:2&removeService=auto-detailing" | jq
# Remove multiple products
curl -s "https://pops-personal-touch.lovable.app/api/cart?baseProducts=foam-shampoo:2,tire-shine:1&removeProduct=foam-shampoo,tire-shine" | jq
# Same operations via POST
curl -X POST "https://pops-personal-touch.lovable.app/api/cart" \
-H "Content-Type: application/json" \
-d '{ "action":"remove", "baseServices":["auto-detailing","interior-detail"], "baseProducts":[{"id":"foam-shampoo","qty":2}], "removeServices":["auto-detailing"] }'
curl -X POST "https://pops-personal-touch.lovable.app/api/cart" \
-H "Content-Type: application/json" \
-d '{ "action":"clear" }'postMessage equivalents
// Remove specific IDs
frame.contentWindow.postMessage({
source: 'pops-cart',
action: 'remove',
services: ['auto-detailing'],
products: [{ id: 'foam-shampoo' }],
}, 'https://pops-personal-touch.lovable.app');
// Clear everything
frame.contentWindow.postMessage({
source: 'pops-cart',
action: 'clear',
}, 'https://pops-personal-touch.lovable.app');2. postMessage API
From a parent window (with the site in an iframe) or an opener/opened window, send a message event with source: "pops-cart".
Message Envelope
{
"source": "pops-cart",
"action": "add" | "remove" | "clear" | "get",
"services": ["service-id-1", "service-id-2"],
"products": [{ "id": "product-id", "qty": 2 }],
"requestId": "optional-correlation-id"
}Actions
| Action | Payload | Response |
|---|---|---|
add | services and/or products | { ok, addedServices, addedProducts } |
remove | services and/or products | { ok: true } |
clear | β | { ok: true } |
get | β | { services, products } |
Example β from a parent page hosting an iframe
<iframe id="pops" src="https://pops-personal-touch.lovable.app/" style="width:100%;height:600px;border:0"></iframe>
<script>
const frame = document.getElementById('pops');
frame.addEventListener('load', () => {
frame.contentWindow.postMessage({
source: 'pops-cart',
action: 'add',
services: ['auto-detailing'],
products: [{ id: 'foam-shampoo', qty: 2 }],
requestId: 'req-1'
}, 'https://pops-personal-touch.lovable.app');
});
window.addEventListener('message', (e) => {
if (e.data?.source === 'pops-cart' && e.data.requestId === 'req-1') {
console.log('Cart response:', e.data.result);
}
});
</script>3. Global Helper (same-origin)
When your script runs inside the site, window.PopsCart is available.
window.PopsCart.add({
services: ['auto-detailing', 'interior-detail'],
products: [{ id: 'foam-shampoo', qty: 2 }],
});
window.PopsCart.get(); // { services, products }
window.PopsCart.remove({ services: ['interior-detail'] });
window.PopsCart.clear();4. GET /api/cart β JSON summary
Server endpoint that computes cart contents, line totals, tax (6%), and grand total from the same query params as the URL API. Stateless β it does not read anyone's browser cart; it just prices the IDs you send.
Endpoint
GET https://pops-personal-touch.lovable.app/api/cart?addService=<id,id>&addProduct=<id:qty,id:qty>
POST https://pops-personal-touch.lovable.app/api/cart Content-Type: application/json
body: { "services": ["id"], "products": [{ "id": "x", "qty": 2 }] }Response shape
{
"ok": true,
"input": { "services": ["auto-detailing"], "products": [{ "id": "foam-shampoo", "qty": 2 }] },
"services": [{ "id","kind":"service","title","icon","qty","unitPrice","lineTotal","meta":{"durationMin","rate"} }],
"products": [{ "id","kind":"product","title","icon","qty","unitPrice","lineTotal" }],
"subtotal": 258.98,
"tax": 15.54,
"taxRate": 0.06,
"total": 274.52,
"itemCount": 3,
"unknownIds": []
}curl β URL params (GET)
# Single service
curl "https://pops-personal-touch.lovable.app/api/cart?addService=auto-detailing"
# Multiple services
curl "https://pops-personal-touch.lovable.app/api/cart?addService=auto-detailing,interior-detail,exterior-detail"
# Product with quantity
curl "https://pops-personal-touch.lovable.app/api/cart?addProduct=foam-shampoo:2"
# Multiple products
curl "https://pops-personal-touch.lovable.app/api/cart?addProduct=foam-shampoo:2,tire-shine:1,ceramic-wax:3"
# Mixed services + products
curl "https://pops-personal-touch.lovable.app/api/cart?addService=auto-detailing,interior-detail&addProduct=foam-shampoo:2,tire-shine:1"
# Pretty-print with jq
curl -s "https://pops-personal-touch.lovable.app/api/cart?addService=auto-detailing&addProduct=foam-shampoo:2" | jqcurl β postMessage-equivalent payload (POST)
postMessage runs in-browser, not over HTTP β but the same envelope works against POST /api/cart:
curl -X POST "https://pops-personal-touch.lovable.app/api/cart" \
-H "Content-Type: application/json" \
-d '{
"services": ["auto-detailing", "interior-detail"],
"products": [{ "id": "foam-shampoo", "qty": 2 }, { "id": "tire-shine", "qty": 1 }]
}'Item IDs
Service and product IDs come from Manage Services and Manage Products. Current catalog:
Services
auto-detailingcarpet-shampooinginterior-detailexterior-detailclay-bar-treatmentheadlight-restorationengine-bay-cleaning
Products
foam-shampoowash-mittgrit-guardbug-tar-removerceramic-waxcarnauba-pasteclay-barpolish-compounddetail-spraytire-shinewheel-cleanerwheel-brushinterior-kitleather-conditionercarpet-shampooglass-cleanermicrofiber-setwiper-bladesled-bulbsjump-starterfloor-matsair-freshener
ποΈ Coupon Landing Page API
GET /api/public/coupon renders a fully-styled, CORS-friendly coupon landing page you can link to, iframe, or share from any external site. The "Redeem" button hands off to /api/cart (the CartAPI documented above) using the same addService / addProduct URL params, so the landing page and the main store share one source of truth.
ποΈ Open Coupon Dashboard β Build, save, edit, duplicate and share reusable coupons.
Query params
| Param | Example | Purpose |
|---|---|---|
code | SUMMER25 | Coupon code shown big & bold. |
title | Summer Shine Sale | Headline. |
subtitle | Ends Sunday | Secondary line. |
discount | 25 | Percent-off; drives the savings math. |
discountLabel | $25 OFF | Free-form badge (overrides discount). |
expires | 2026-12-31 | Displayed as "Expires β¦". |
services | auto-detailing,clay-bar-treatment | Comma-separated service IDs. |
products | foam-shampoo:2,ceramic-wax:1 | Product IDs with optional :qty. |
cta | Redeem Now | Button label. |
theme | dark / light | Landing page palette. |
accent | %23ef4444 | Hex accent color (URL-encode #). |
logo | https://β¦/logo.png | Optional logo image URL. |
returnUrl | https://your.site/thanks | Override the Redeem destination. |
format | json | Return JSON payload instead of HTML. |
Testing URLs
Launch URL
https://pops-personal-touch.lovable.app/api/public/coupon?code=YOUR-CODE&title=Your%20Offer&discount=15&services=<id,id>&products=<id:qty,id:qty>Share this URL directly, drop it in email/SMS, or embed the endpoint in an <iframe>. CORS allows any origin.
Notes
- Cart is stored per-browser (
localStorage). It is not synced across devices. - URL params are stripped after intake, so refreshing does not re-add items.
- Modals and cart badges update live via the browser
storageevent.