# Storefront · Checkout

Part of AppEngine API Documentation. Full index: https://appengine.appmint.io/documentation.md
## GET /storefront/cart/mine

**Restore my saved cart**

`operationId: StorefrontController_savedCart`

The signed-in customer's saved cart (`sf_cart`), for restoring a basket on a new device. Identity comes only from the customer session — never from a supplied email or id — and the cart is scoped to the customer's shared account when they have one. Read-only: returns `null` rather than creating a cart.

#### Signature

```http
GET /storefront/cart/mine () -> The saved cart, or null
```

#### Access

Requires a bearer JWT (`Authorization: Bearer <token>`).

#### Errors

| Status | Code | Message | When | What to do |
| --- | --- | --- | --- | --- |
| `401` | SIGN_IN_REQUIRED | Sign in to restore your saved cart | No customer session for this org (a staff/user token does not count). | — |

Plus the standard platform errors: `403`, `429`, `500`.

#### See also

- `GET /storefront/cart/get/{authorid}/{cartid}`

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `orgid` | header | string | yes | Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials. |

### Responses

| Status | Meaning |
| --- | --- |
| `200` | The saved cart, or null |
| `401` | Sign in to restore your saved cart — No customer session for this org (a staff/user token does not count). |
| `403` | You do not have permission to perform this action — The caller is authenticated but lacks the role required by the endpoint, or is acting on another org. |
| `429` | Too Many Requests — More than 100,000 requests from one IP within 5 minutes (configurable per deployment). CORS preflights and requests from inside the platform cluster are not counted. The limiter answers before the error filter, so the body is `{ statusCode, error, message }` with no `path`, `method` or `timeStamp`; the `RateLimit-*` response headers say when the window resets. |
| `500` | An unexpected error occurred. Our team has been notified. — An unhandled server-side failure. |

## POST /storefront/checkout-cart

**Check out a cart**

`operationId: StorefrontController_checkoutCart`

Converts a cart into an order: creates the `sf_order`, records the payment reference, and triggers the order-confirmation notification.

This is the standard product-only checkout. Use `checkout-mixed` when the basket contains rentals, and `checkout-buy-now` to skip the cart entirely.

#### Signature

```http
POST /storefront/checkout-cart (body) -> The created order
```

#### Access

Public — no credentials required.

#### Notes

- Payment is expected to have been taken already — pass its reference in `paymentRef`. Take the payment with `POST /storefront/take-payment` or a Stripe intent first.
- Not idempotent: calling it twice creates two orders. Guard against double submission on the client.

#### Errors

Plus the standard platform errors: `429`, `500`.

#### See also

- `POST /storefront/checkout-mixed`
- `POST /storefront/take-payment`

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `orgid` | header | string | yes | Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials. |

### Request body

Checkout details — contents, addresses, and the payment already taken.

```json
{
  "items": [
    {
      "sku": "DRK-COLA-330",
      "quantity": 2,
      "price": 12
    }
  ],
  "email": "ada@example.com",
  "shippingAddress": {
    "name": "Ada Lovelace",
    "line1": "12 Ada Way",
    "city": "London",
    "postcode": "E1 6AN",
    "country": "GB"
  },
  "total": 30.91,
  "currency": "USD",
  "paymentRef": "pi_3PabcXYZ",
  "paymentGateway": "stripe"
}
```

### Responses

| Status | Meaning |
| --- | --- |
| `201` | The created order |
| `429` | Too Many Requests — More than 100,000 requests from one IP within 5 minutes (configurable per deployment). CORS preflights and requests from inside the platform cluster are not counted. The limiter answers before the error filter, so the body is `{ statusCode, error, message }` with no `path`, `method` or `timeStamp`; the `RateLimit-*` response headers say when the window resets. |
| `500` | An unexpected error occurred. Our team has been notified. — An unhandled server-side failure. |

## POST /storefront/checkout-mixed

**Check out a basket of products and rentals**

`operationId: StorefrontController_checkoutMixed`

Checks out a basket containing both ordinary products and rental items. The two are routed differently: products become lines on a standard order, while rentals are created as **separate rental bookings** with their own dates and availability rules.

Each line declares which it is via `itemType`. Rental lines additionally need `startDate`, `endDate` and `rentalPeriod`. The response carries both the order and the bookings that were created.

#### Signature

```http
POST /storefront/checkout-mixed (body) -> The order and the rental bookings created alongside it
```

#### Access

Public — no credentials required.

#### Notes

- A line with no `itemType` is treated as a product. Rentals must set it explicitly or they will not be booked.

#### Errors

Plus the standard platform errors: `429`, `500`.

#### See also

- `POST /storefront/checkout-cart`

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `orgid` | header | string | yes | Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials. |

### Request body

Mixed basket plus checkout details.

```json
{
  "items": [
    {
      "sku": "DRK-COLA-330",
      "quantity": 2,
      "price": 12,
      "itemType": "product"
    },
    {
      "sku": "CAM-RED-KOMODO",
      "quantity": 1,
      "price": 165.99,
      "itemType": "rental",
      "startDate": "2026-09-01T09:00:00.000Z",
      "endDate": "2026-09-04T17:00:00.000Z",
      "rentalPeriod": "daily"
    }
  ],
  "email": "ada@example.com",
  "total": 189.99,
  "currency": "USD",
  "paymentRef": "pi_3PabcXYZ",
  "paymentGateway": "stripe"
}
```

### Responses

| Status | Meaning |
| --- | --- |
| `201` | The order and the rental bookings created alongside it |
| `429` | Too Many Requests — More than 100,000 requests from one IP within 5 minutes (configurable per deployment). CORS preflights and requests from inside the platform cluster are not counted. The limiter answers before the error filter, so the body is `{ statusCode, error, message }` with no `path`, `method` or `timeStamp`; the `RateLimit-*` response headers say when the window resets. |
| `500` | An unexpected error occurred. Our team has been notified. — An unhandled server-side failure. |

## POST /storefront/checkout-buy-now

**Buy now, without a cart**

`operationId: StorefrontController_checkoutBuyNow`

Single-item express checkout. Creates the order directly from the item in the body, skipping the cart entirely — the "Buy it now" button next to a product.

#### Signature

```http
POST /storefront/checkout-buy-now (body) -> The created order
```

#### Access

Public — no credentials required.

#### Errors

Plus the standard platform errors: `429`, `500`.

#### See also

- `POST /storefront/checkout-cart`

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `orgid` | header | string | yes | Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials. |

### Request body

The item and the checkout details.

```json
{
  "sku": "DRK-COLA-330",
  "quantity": 1,
  "email": "ada@example.com",
  "paymentRef": "pi_3PabcXYZ",
  "paymentGateway": "stripe"
}
```

### Responses

| Status | Meaning |
| --- | --- |
| `201` | The created order |
| `429` | Too Many Requests — More than 100,000 requests from one IP within 5 minutes (configurable per deployment). CORS preflights and requests from inside the platform cluster are not counted. The limiter answers before the error filter, so the body is `{ statusCode, error, message }` with no `path`, `method` or `timeStamp`; the `RateLimit-*` response headers say when the window resets. |
| `500` | An unexpected error occurred. Our team has been notified. — An unhandled server-side failure. |

