docs
/
Client Integration

Quickstart

Your first working call, then sign in a customer, list products, price a cart and place an order — with runnable requests and real responses.

Every request needs two things: a host and an orgid.

Value
Hosthttps://appengine.appmint.io
Tenant headerorgid: <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 it

This 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: