Every request needs two things: a host and an orgid.
| Value | |
|---|---|
| Host | https://appengine.appmint.io |
| Tenant header | orgid: <your-org> |
orgid is your organization id — it identifies your tenant, and every request is scoped to it. You can see it in Studio Manager.
Set it once so the requests on this page run as-is:
export APPMINT_ORG="your-org-id"1. Your first call
Products are readable without a customer session, so this works immediately:
curl -s https://appengine.appmint.io/storefront/products?ps=1 \
-H "orgid: $APPMINT_ORG"Leave out orgid and you get a precise refusal rather than an empty list:
{
"code": "missing_orgid",
"statusCode": 400,
"error": "Organization ID is required. Pass orgid as a header, query param, or body field.",
"path": "/",
"method": "GET",
"timeStamp": "2026-09-11T09:16:21.511Z"
}2. The response envelope
A list returns paging metadata at the top level and the rows under data:
{
"total": 90,
"datatype": "sf_product",
"pageSize": 1,
"page": 1,
"sort": "modifydate",
"sortType": -1,
"hasNext": true,
"fromCache": false,
"lastItem": "2026-09-11T03:02:38.374Z",
"minMaxPrice": { "minPrice": 3, "maxPrice": 6000 },
"data": [ /* records */ ]
}Page with p and ps:
curl -s "https://appengine.appmint.io/storefront/products?p=2&ps=20" -H "orgid: $APPMINT_ORG"Each row is a BaseModel — platform fields at the top, your payload under data:
{
"datatype": "sf_product",
"name": "Test Unconfigured",
"pk": "<your-org>|sf_product",
"sk": "6aa36f4ebe034e25f159b4e9",
"createdate": "2026-09-11T03:02:38.374Z",
"modifydate": "2026-09-11T03:02:38.374Z",
"version": 0,
"data": {
"sku": "ST-NOSHIP",
"name": "Test Unconfigured",
"price": 30,
"status": "active",
"parcel": { "weight": 2, "weightUnit": "lb", "length": 8, "width": 6, "height": 4 },
"calculatedPrice": {
"originalPrice": 30, "finalPrice": 30,
"discount": 0, "discountPercent": 0, "appliedDiscounts": []
}
}
}sk is the record id you pass to other endpoints. data.price is the list price; data.calculatedPrice.finalPrice is what this customer pays after price lists and rules. Show finalPrice.
3. Sign a customer in
curl -s https://appengine.appmint.io/profile/customer/signin \
-H "orgid: $APPMINT_ORG" -H "Content-Type: application/json" \
-d '{"email":"[email protected]","password":"…"}'Returns a token plus the customer record. Keep the token server-side and send it as x-client-authorization on later calls — see Authentication.
Calling a customer route without it is explicit:
{
"code": "missing_authorization_header",
"statusCode": 401,
"error": "Authorization header is required. Send requests with `Authorization: Bearer <token>`.",
"path": "/client/affiliate/me",
"method": "GET",
"timeStamp": "2026-09-11T09:16:32.429Z"
}4. Price a cart
This is the call that decides money. Send the items, the destination and the coupon together — the server nets the discount, resolves shipping and computes tax in one pass.
curl -s https://appengine.appmint.io/storefront/pricing/calculate-cart \
-H "orgid: $APPMINT_ORG" -H "Content-Type: application/json" \
-d '{
"productItems": [
{ "sku": "ST-SOFA", "name": "Test Sofa", "price": 900,
"unitPrice": 900, "quantity": 1, "itemType": "product" }
],
"shippingAddress": {
"street1": "1 Main St", "city": "Dallas",
"state": "TX", "zip": "75001", "country": "US"
},
"couponCode": "SPRING10"
}'Real response:
{
"subtotal": 900,
"discount": 0,
"tax": 74.25,
"productShipping": 75,
"total": 1049.25,
"shippingMethod": "flat",
"freeShipping": false,
"valid": true
}Render total as given. It already includes tax and shipping. subtotal + productShipping gives 975 and silently loses 74.25 of tax — and matches on tax-free destinations, which is why that bug survives review.
A bad coupon is reported, never thrown, and leaves the money untouched:
{ "valid": false, "reason": "not_found", "message": "Discount code not found", "total": 1049.25 }5. Check out
curl -s https://appengine.appmint.io/storefront/checkout-cart \
-H "orgid: $APPMINT_ORG" -H "Content-Type: application/json" \
-H "x-client-authorization: <customer-token>" \
-d '{ "productItems": [...], "shippingAddress": {...}, "paymentGateway": "stripe" }'Then read it back:
curl -s "https://appengine.appmint.io/storefront/order/get?orderNumber=…" \
-H "orgid: $APPMINT_ORG" -H "x-client-authorization: <customer-token>"A minimal client
const HOST = 'https://appengine.appmint.io';
const ORG = process.env.APPMINT_ORG;
export async function appmint(path, { method = 'GET', body, customerToken } = {}) {
const res = await fetch(`${HOST}/${path.replace(/^\//, '')}`, {
method,
headers: {
orgid: ORG,
'Content-Type': 'application/json',
...(process.env.APPMINT_API_KEY ? { apiKey: process.env.APPMINT_API_KEY } : {}),
...(customerToken ? { 'x-client-authorization': customerToken } : {}),
},
body: body ? JSON.stringify(body) : undefined,
});
const payload = await res.json().catch(() => null);
if (!res.ok) {
// `code` is stable; `error` is human-readable.
const err = new Error(payload?.error ?? res.statusText);
err.code = payload?.code;
err.status = res.status;
throw err;
}
return payload;
}const { data: products } = await appmint('storefront/products?ps=20');
const summary = await appmint('storefront/pricing/calculate-cart', {
method: 'POST',
body: { productItems, shippingAddress, couponCode },
});
console.log(summary.total); // render this, don't rebuild itThis runs on your server, never in a browser — it carries your apiKey. See Overview for the call path and The server proxy for exposing it to your frontend safely.
Next
That client is deliberately hand-rolled so you can see every header. In a real app use the library instead, which handles the app token, the two-transport split and error shapes for you:
- Build a storefront with the TypeScript client — the same flows, end to end, with
@appmint/js-client - Example apps — clone a working storefront and point it at your organization
- Endpoint reference — everything else you can call