# Stowbo · Browse

Part of AppEngine API Documentation. Full index: https://appengine.appmint.io/documentation.md
## GET /client/stowbo/listings

**Search places**

`operationId: StowboClientController_browse`

Finds places to book. **Dates change what this means.** With `startDate` and `endDate` the results are restricted to what can actually take the booking for that window — full, blacked-out and tree-blocked listings are excluded — and each result carries a real price for that stay.

Without dates it is a plain catalogue browse making **no availability or price claims**, because a banded rate has no single price to advertise. A UI that shows prices from a dateless search is showing something the server did not promise.

Search geographically by viewport (`swLat`/`swLng`/`neLat`/`neLng`, sent together as the map pans) or by point (`lat`/`lng` with `radiusKm`).

#### Signature

```http
GET /client/stowbo/listings (city?: string, spaceType?: string, keyword?: string, startDate?: string, endDate?: string, quantity?: integer, lat?: number, lng?: number, radiusKm?: number, swLat?: number, swLng?: number, neLat?: number, neLng?: number, page?: integer, pageSize?: integer) -> Matching listings — priced only when dates were given
```

#### Access

Public — no credentials required.

#### Notes

- Without dates, results carry no availability or price guarantee.

#### Errors

| Status | Code | Message | When | What to do |
| --- | --- | --- | --- | --- |
| `400` | NO_COORDINATES | No coordinates to search from | A geographic search was requested without a usable point or viewport. | Send `lat`/`lng`, or all four viewport corners. |

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

#### See also

- `GET /client/stowbo/listings/{listingId}`

### 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. |
| `city` | query | string | — |  |
| `spaceType` | query | string | — |  |
| `keyword` | query | string | — |  |
| `startDate` | query | string | — | ISO date. With `endDate`, filters to what is genuinely bookable then and prices it for that window. |
| `endDate` | query | string | — | ISO date. |
| `quantity` | query | integer | — | How many are needed. |
| `lat` | query | number | — | "Near me" — a point instead of a viewport. |
| `lng` | query | number | — |  |
| `radiusKm` | query | number | — | With `lat`/`lng`. Default 10, max 200. |
| `swLat` | query | number | — | Map viewport corner — send all four as the customer pans and zooms. |
| `swLng` | query | number | — |  |
| `neLat` | query | number | — |  |
| `neLng` | query | number | — |  |
| `page` | query | integer | — |  |
| `pageSize` | query | integer | — |  |

### Responses

| Status | Meaning |
| --- | --- |
| `200` | Matching listings — priced only when dates were given |
| `400` | No coordinates to search from — A geographic search was requested without a usable point or viewport. |
| `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. |

## GET /client/stowbo/listings/{listingId}/nearby

**Find similar places nearby**

`operationId: StowboClientController_nearby`

Comparable places around a listing. Defaults to the anchor listing's own space type and coordinates; pass `lat`/`lng` to search from a different point, and dates to get back only what is free then, priced for it.

#### Signature

```http
GET /client/stowbo/listings/{listingId}/nearby (listingId: string, radiusKm?: number, spaceType?: string, lat?: number, lng?: number, startDate?: string, endDate?: string, quantity?: integer, pageSize?: integer) -> Nearby listings
```

#### Access

Public — no credentials required.

#### Errors

| Status | Code | Message | When | What to do |
| --- | --- | --- | --- | --- |
| `404` | LISTING_NOT_FOUND | Listing not found | No listing has that id. | Search listings first. |

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

#### See also

- `GET /client/stowbo/listings/{listingId}/more-from-host`

### 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. |
| `listingId` | path | string | yes | Listing id. |
| `radiusKm` | query | number | — | Default 5, max 200. |
| `spaceType` | query | string | — | Defaults to the anchor listing's type. |
| `lat` | query | number | — |  |
| `lng` | query | number | — |  |
| `startDate` | query | string | — | ISO date. With `endDate`, filters to what is genuinely bookable then and prices it for that window. |
| `endDate` | query | string | — | ISO date. |
| `quantity` | query | integer | — | How many are needed. |
| `pageSize` | query | integer | — |  |

### Responses

| Status | Meaning |
| --- | --- |
| `200` | Nearby listings |
| `404` | Listing not found — No listing has that id. |
| `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. |

## GET /client/stowbo/listings/{listingId}/more-from-host

**Get other places by the same host**

`operationId: StowboClientController_moreFromHost`

This host's other active listings, excluding the current one, plus a guest-safe host card. Pass dates to get them availability-filtered and priced for the window.

#### Signature

```http
GET /client/stowbo/listings/{listingId}/more-from-host (listingId: string, startDate?: string, endDate?: string, quantity?: integer, page?: integer, pageSize?: integer) -> The host's other listings and their card
```

#### Access

Public — no credentials required.

#### Errors

| Status | Code | Message | When | What to do |
| --- | --- | --- | --- | --- |
| `404` | LISTING_NOT_FOUND | Listing not found | No listing has that id. | Search listings first. |

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

#### See also

- `GET /client/stowbo/listings/{listingId}/nearby`

### 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. |
| `listingId` | path | string | yes | Listing id. |
| `startDate` | query | string | — | ISO date. With `endDate`, filters to what is genuinely bookable then and prices it for that window. |
| `endDate` | query | string | — | ISO date. |
| `quantity` | query | integer | — | How many are needed. |
| `page` | query | integer | — |  |
| `pageSize` | query | integer | — |  |

### Responses

| Status | Meaning |
| --- | --- |
| `200` | The host's other listings and their card |
| `404` | Listing not found — No listing has that id. |
| `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. |

## GET /client/stowbo/listings/{listingId}

**Get a place**

`operationId: StowboClientController_listing`

A listing, what is bookable inside it, and the add-ons that apply to it.

#### Signature

```http
GET /client/stowbo/listings/{listingId} (listingId: string) -> The listing with its units and add-ons
```

#### Access

Public — no credentials required.

#### Errors

| Status | Code | Message | When | What to do |
| --- | --- | --- | --- | --- |
| `404` | LISTING_NOT_FOUND | Listing not found | No listing has that id. | Search listings first. |

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

#### See also

- `GET /client/stowbo/listings/{listingId}/availability`

### 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. |
| `listingId` | path | string | yes | Listing id. |

### Responses

| Status | Meaning |
| --- | --- |
| `200` | The listing with its units and add-ons |
| `404` | Listing not found — No listing has that id. |
| `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. |

## GET /client/stowbo/chat-config

**Get the support chat settings**

`operationId: StowboClientController_chatConfig`

The chat config id and app id the Stowbo site points at. The app opens its customer-to-admin support chat with these; an admin changes them on the site.

#### Signature

```http
GET /client/stowbo/chat-config () -> Chat settings (each null when not set)
```

#### Access

Public — no credentials required.

#### Errors

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

### 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. |
| `addOns` | query | any | — | JSON array of chosen add-ons, for the quote |
| `quantity` | query | any | — |  |
| `endDate` | query | any | yes |  |
| `startDate` | query | any | yes |  |
| `listingId` | path | any | yes |  |

### Responses

| Status | Meaning |
| --- | --- |
| `200` | Chat settings (each null when not set) |
| `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. |

Example response:

```json
{
  "configId": "66f1c0ffee",
  "appId": "stowbo-support",
  "site": "stowbo"
}
```

## GET /client/stowbo/pickup/{token}

**Get a pickup pass**

`operationId: StowboClientController_pickupPass`

What the collector shows and where to go: the items, the place (address, access hours, instructions), what will be checked (`verify`; `bringId` when photo ID is needed), and — only while the pass is active — the code and QR payload the host scans. No sign-in; never the owner's identity.

#### Signature

```http
GET /client/stowbo/pickup/{token} (token: string) -> The pass
```

#### Access

Public — no credentials required.

#### Errors

| Status | Code | Message | When | What to do |
| --- | --- | --- | --- | --- |
| `404` | PASS_INVALID | This pass is not valid | The token matches no hand-off (or was replaced by a resend). | — |

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

### 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. |
| `token` | path | string | yes | The pass token from the pickup link. |

### Responses

| Status | Meaning |
| --- | --- |
| `200` | The pass |
| `404` | This pass is not valid — The token matches no hand-off (or was replaced by a resend). |
| `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. |

## GET /client/stowbo/pickup/{token}/receipt

**Get the receipt for a collected pickup**

`operationId: StowboClientController_pickupReceipt`

After collection: who collected, where, when, and for each item when it was handed over, by whom, what was verified and the photos.

#### Signature

```http
GET /client/stowbo/pickup/{token}/receipt (token: string) -> The receipt
```

#### Access

Public — no credentials required.

#### Errors

| Status | Code | Message | When | What to do |
| --- | --- | --- | --- | --- |
| `404` | PASS_INVALID | This pass is not valid | Unknown token. | — |
| `409` | NOT_COLLECTED | Nothing collected yet | The hand-off has not been completed. | — |

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

### 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. |
| `token` | path | string | yes | The pass token from the pickup link. |

### Responses

| Status | Meaning |
| --- | --- |
| `200` | The receipt |
| `404` | This pass is not valid — Unknown token. |
| `409` | Nothing collected yet — The hand-off has not been completed. |
| `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. |

## GET /client/stowbo/listings/{listingId}/availability

**Check availability for a window**

`operationId: StowboClientController_availability`

How many of a listing are free for a window, and what it would cost. Pass `addOns` as a JSON array of chosen add-ons to have them priced in.

#### Signature

```http
GET /client/stowbo/listings/{listingId}/availability (listingId: string, startDate?: string, endDate?: string, quantity?: integer, addOns?: string) -> Free count and price for the window
```

#### Access

Public — no credentials required.

#### Errors

| Status | Code | Message | When | What to do |
| --- | --- | --- | --- | --- |
| `404` | LISTING_NOT_FOUND | Listing not found | No listing has that id. | Search listings first. |

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

#### See also

- `POST /client/stowbo/checkout/quote`

### 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. |
| `listingId` | path | string | yes | Listing id. |
| `startDate` | query | string | yes |  |
| `endDate` | query | string | — |  |
| `quantity` | query | integer | — |  |
| `addOns` | query | string | — | JSON array of chosen add-ons, for the quote. |
| `discountCode` | query | string | yes |  |
| `customer` | query | string | yes |  |

### Responses

| Status | Meaning |
| --- | --- |
| `200` | Free count and price for the window |
| `404` | Listing not found — No listing has that id. |
| `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. |

## GET /client/stowbo/listings/{listingId}/calendar

**Get a place's availability calendar**

`operationId: StowboClientController_calendar`

A day-by-day availability grid, computed server-side and safe to show a guest — it exposes what is free, not who booked it.

#### Signature

```http
GET /client/stowbo/listings/{listingId}/calendar (listingId: string, from?: string, days?: integer) -> The calendar
```

#### Access

Public — no credentials required.

#### Errors

| Status | Code | Message | When | What to do |
| --- | --- | --- | --- | --- |
| `404` | LISTING_NOT_FOUND | Listing not found | No listing has that id. | Search listings first. |

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

#### See also

- `GET /client/stowbo/host/listings/{id}/calendar`

### 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. |
| `listingId` | path | string | yes | Listing id. |
| `from` | query | string | — | ISO start date. Defaults to today. |
| `days` | query | integer | — | How many days to return. |

### Responses

| Status | Meaning |
| --- | --- |
| `200` | The calendar |
| `404` | Listing not found — No listing has that id. |
| `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 /client/stowbo/checkout/suggestions

**Get mid-booking suggestions**

`operationId: StowboClientController_suggestions`

Two lists derived from the cart's own listings, dates and location: `alternatives` (other places of the same kind nearby — so the comparison the guest was about to go and make happens here instead) and `complements` (a different kind nearby, for the one-trip bundle).

Both are availability-checked and priced for the cart's window, so adding one is a single click. Ordered by relevance.

#### Signature

```http
POST /client/stowbo/checkout/suggestions (body) -> `alternatives` and `complements`
```

#### Access

Public — no credentials required.

#### Errors

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

#### See also

- `POST /client/stowbo/checkout/quote`

### 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 cart.

```json
{
  "lines": [
    {
      "listingId": "LST-4821",
      "startDate": "2026-09-05",
      "endDate": "2026-09-12",
      "quantity": 1
    }
  ]
}
```

### Responses

| Status | Meaning |
| --- | --- |
| `201` | `alternatives` and `complements` |
| `409` | Something in the cart went unavailable |
| `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. |

## GET /client/stowbo/pay/{ref}

**Get what a payment link is for**

`operationId: StowboClientController_paymentView`

Backs the signed-out take-payment page. `ref` is a payment **reference**, resolved in order as an ad-hoc payment request's pay token, then a ledger row `sk`, then a booking id — one endpoint serves link/QR requests and booking balances alike. The server derives the amount due; a link's amount is display-only and never trusted.

Deliberately thin: what the charge is for and the balance. **No guest identity, no access codes** — anyone with the reference can load it.

#### Signature

```http
GET /client/stowbo/pay/{ref} (ref: string) -> What is owed, and what for
```

#### Access

Public — no credentials required.

#### Notes

- Public and deliberately minimal — no identity or codes.

#### Errors

| Status | Code | Message | When | What to do |
| --- | --- | --- | --- | --- |
| `404` | BOOKING_NOT_FOUND | Booking <ref> not found | The reference matches no payment request, ledger row or booking. | List your bookings. |
| `400` | REF_REQUIRED | ref is required | Empty reference. | — |

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

#### See also

- `POST /client/stowbo/pay/{ref}`

### 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. |
| `ref` | path | string | yes | Payment reference: a payment request pay token, a ledger row `sk`, or a booking id. |

### Responses

| Status | Meaning |
| --- | --- |
| `200` | What is owed, and what for |
| `400` | ref is required — Empty reference. |
| `404` | Booking <ref> not found — The reference matches no payment request, ledger row or booking. |
| `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. |

Example response:

```json
{
  "booking": "66f1a2b3c4d5e6f708192a3b",
  "reference": "STW-4821",
  "venue": "Downtown Lockers",
  "currency": "USD",
  "total": 45,
  "paid": 20,
  "balance": 25,
  "status": "partial",
  "cancelled": false,
  "lines": [
    {
      "label": "Locker",
      "amount": 45
    }
  ]
}
```

## POST /client/stowbo/pay/{ref}

**Pay what a payment reference owes**

`operationId: StowboClientController_payBooking`

Charges the **server-computed** balance for `ref` (a payment request pay token, a ledger row `sk`, or a booking id — see the GET). The client cannot name the amount. Pass `paymentMethodId` for a web card, or a confirmed `paymentIntentId` for native or wallet payments. A booking with nothing owing returns `alreadySettled: true` rather than an error.

No sign-in: a guest can settle from a payment link.

#### Signature

```http
POST /client/stowbo/pay/{ref} (ref: string, body) -> The payment summary after paying, with `paid` (amount charged) — or `alreadySettled: true`
```

#### Access

Public — no credentials required.

#### Notes

- Unauthenticated payment route; the amount is always computed on the server.

#### Errors

| Status | Code | Message | When | What to do |
| --- | --- | --- | --- | --- |
| `404` | BOOKING_NOT_FOUND | Booking <ref> not found | The reference matches nothing. | List your bookings. |
| `400` | REF_REQUIRED | ref is required | Empty reference. | — |
| `402` | PAYMENT_REQUIRED | <currency> <balance> is due. | A balance is owed and neither `paymentMethodId` nor `paymentIntentId` was sent. The body carries `dueNow` and `currency`. | — |

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

#### See also

- `GET /client/stowbo/pay/{ref}`

### 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. |
| `ref` | path | string | yes | Payment reference: a payment request pay token, a ledger row `sk`, or a booking id. |

### Request body

The payment.

```json
{
  "paymentMethodId": "pm_1Abc123"
}
```

### Responses

| Status | Meaning |
| --- | --- |
| `201` | The payment summary after paying, with `paid` (amount charged) — or `alreadySettled: true` |
| `400` | ref is required — Empty reference. |
| `402` | <currency> <balance> is due. — A balance is owed and neither `paymentMethodId` nor `paymentIntentId` was sent. The body carries `dueNow` and `currency`. |
| `404` | Booking <ref> not found — The reference matches nothing. |
| `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. |

