# Stowbo · Calendar

Part of AppEngine API Documentation. Full index: https://appengine.appmint.io/documentation.md
## GET /stowbo/listing/{listing}/calendar

**Get the occupancy calendar for a space**

`operationId: StowboController_calendar`

A day-by-day occupancy grid for a listing, computed on the server so the operator console, the host app and the booking screen cannot disagree about what is free.

A site holds no bookings of its own — its child listings do — so asking a site sums its children's capacity and reads their bookings. Asking a leaf listing reports that listing alone.

It answers per unit (`units[].days[]`: `free`, `blocked`, or the state of the booking on that unit) and per day (`capacityByDay`), plus today's numbers, the next free day and the next arrival. An open (metered) stay occupies every day from its start.

#### Signature

```http
GET /stowbo/listing/{listing}/calendar (listing: string, from?: string, days?: integer) -> The occupancy grid
```

#### Access

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

#### Notes

- `days` is clamped to 120. Asking for a year returns 120 days without warning.

#### Errors

| Status | Code | Message | When | What to do |
| --- | --- | --- | --- | --- |
| `404` | LISTING_NOT_FOUND | Listing <name> not found | No listing in the org has that name or id. | Listings are ordinary records — list them through the repository API for `stowbo_listing`. |

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

#### See also

- `GET /stowbo/listing/{listing}/blackouts`
- `GET /stowbo/calendar`
- `GET /stowbo/overdue`

### 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. |
| `listing` | path | string | yes | Listing `name`. A site aggregates its child listings; a leaf listing stands alone. |
| `from` | query | string | — | First day of the window. Snapped to the start of that day. Defaults to today. |
| `days` | query | integer | — | How many days to return. Clamped to 1–120 without an error. |

### Responses

| Status | Meaning |
| --- | --- |
| `200` | The occupancy grid |
| `401` | Authentication failed: Invalid or expired token — The `Authorization` header is missing, malformed, or the JWT has expired. |
| `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. |
| `404` | Listing <name> not found — No listing in the org has that name or 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 /stowbo/listing/{listing}/blackouts

**List the days a space is closed**

`operationId: StowboController_listBlackouts`

Returns the closure windows recorded on a listing. Blackouts live on the listing record itself, so they are returned as stored, in insertion order.

#### Signature

```http
GET /stowbo/listing/{listing}/blackouts (listing: string) -> The listing's closures
```

#### Access

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

#### Errors

| Status | Code | Message | When | What to do |
| --- | --- | --- | --- | --- |
| `404` | LISTING_NOT_FOUND | Listing <name> not found | No listing in the org has that name or id. | Listings are ordinary records — list them through the repository API for `stowbo_listing`. |

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

#### See also

- `POST /stowbo/listing/{listing}/blackouts`

### 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. |
| `listing` | path | string | yes | Listing `name`. A site aggregates its child listings; a leaf listing stands alone. |

### Responses

| Status | Meaning |
| --- | --- |
| `200` | The listing's closures |
| `401` | Authentication failed: Invalid or expired token — The `Authorization` header is missing, malformed, or the JWT has expired. |
| `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. |
| `404` | Listing <name> not found — No listing in the org has that name or 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 /stowbo/listing/{listing}/blackouts

**Close a space for servicing**

`operationId: StowboController_addBlackout`

Adds a closure window to a listing.

**Bookings already inside the window are not cancelled.** They come back in `conflicts` for the operator to resolve — a system that quietly voids paid bookings to make a maintenance window fit is worse than one that reports the clash. The blackout is still written; it is your job to move or cancel the guests it names.

Conflicts are checked across the listing **and its descendants**, so closing a site surfaces bookings held by its child listings.

#### Signature

```http
POST /stowbo/listing/{listing}/blackouts (listing: string, body) -> The closure, the full list, and any bookings caught inside it
```

#### Access

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

#### Notes

- A non-empty `conflicts` array is not an error and does not prevent the closure — always inspect it after closing a space.

#### Errors

| Status | Code | Message | When | What to do |
| --- | --- | --- | --- | --- |
| `404` | LISTING_NOT_FOUND | Listing <name> not found | No listing in the org has that name or id. | Listings are ordinary records — list them through the repository API for `stowbo_listing`. |
| `400` | WINDOW_REQUIRED | from and to are required | Either bound is missing. | Send both ends of the closure. |

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

#### See also

- `DELETE /stowbo/listing/{listing}/blackouts/{blackoutId}`
- `GET /stowbo/listing/{listing}/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. |
| `listing` | path | string | yes | Listing `name`. A site aggregates its child listings; a leaf listing stands alone. |

### Request body

The window to close.

```json
{
  "from": "2026-10-01T00:00:00.000Z",
  "to": "2026-10-03T00:00:00.000Z",
  "reason": "servicing",
  "note": "Annual lock replacement"
}
```

### Responses

| Status | Meaning |
| --- | --- |
| `201` | The closure, the full list, and any bookings caught inside it |
| `400` | from and to are required — Either bound is missing. |
| `401` | Authentication failed: Invalid or expired token — The `Authorization` header is missing, malformed, or the JWT has expired. |
| `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. |
| `404` | Listing <name> not found — No listing in the org has that name or 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. |

## DELETE /stowbo/listing/{listing}/blackouts/{blackoutId}

**Reopen a space**

`operationId: StowboController_removeBlackout`

Removes a closure from a listing, making those days bookable again. Returns the remaining closures.

#### Signature

```http
DELETE /stowbo/listing/{listing}/blackouts/{blackoutId} (listing: string, blackoutId: string) -> The remaining closures
```

#### Access

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

#### Notes

- Removing an id that does not exist succeeds and returns the unchanged list — it is not a `404`.

#### Errors

| Status | Code | Message | When | What to do |
| --- | --- | --- | --- | --- |
| `404` | LISTING_NOT_FOUND | Listing <name> not found | No listing in the org has that name or id. | Listings are ordinary records — list them through the repository API for `stowbo_listing`. |

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

#### See also

- `POST /stowbo/listing/{listing}/blackouts`

### 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. |
| `listing` | path | string | yes | Listing `name`. A site aggregates its child listings; a leaf listing stands alone. |
| `blackoutId` | path | string | yes | Closure id, from the blackout list. |

### Responses

| Status | Meaning |
| --- | --- |
| `200` | The remaining closures |
| `401` | Authentication failed: Invalid or expired token — The `Authorization` header is missing, malformed, or the JWT has expired. |
| `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. |
| `404` | Listing <name> not found — No listing in the org has that name or 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 /stowbo/availability

**Check how many of a space are free**

`operationId: StowboController_availability`

How many of a listing are free for a window, after existing bookings, blackouts and closures up the listing tree. The same check a booking makes.

#### Signature

```http
GET /stowbo/availability (listing?: string, startDate?: string, endDate?: string, quantity?: integer) -> Availability for the window (`available`, and `blackout` / `blockedByTree` when a closure is the reason)
```

#### Access

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

#### Errors

| Status | Code | Message | When | What to do |
| --- | --- | --- | --- | --- |
| `404` | LISTING_NOT_FOUND | Listing <name> not found | No listing in the org has that name or id. | Listings are ordinary records — list them through the repository API for `stowbo_listing`. |

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

#### See also

- `GET /stowbo/listing/{listing}/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. |
| `listing` | query | string | yes | Listing name. |
| `startDate` | query | string | yes |  |
| `endDate` | query | string | — | Omit for an open (metered) stay. |
| `quantity` | query | integer | — |  |

### Responses

| Status | Meaning |
| --- | --- |
| `200` | Availability for the window (`available`, and `blackout` / `blockedByTree` when a closure is the reason) |
| `401` | Authentication failed: Invalid or expired token — The `Authorization` header is missing, malformed, or the JWT has expired. |
| `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. |
| `404` | Listing <name> not found — No listing in the org has that name or 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 /stowbo/listing/{listing}/addons

**List the add-ons a space offers**

`operationId: StowboController_listingAddOns`

The add-ons a listing offers — its own plus every ancestor's, since a site's add-ons apply to the spaces inside it.

#### Signature

```http
GET /stowbo/listing/{listing}/addons (listing: string) -> Resolved add-ons
```

#### Access

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

#### Errors

| Status | Code | Message | When | What to do |
| --- | --- | --- | --- | --- |
| `404` | LISTING_NOT_FOUND | Listing <name> not found | No listing in the org has that name or id. | Listings are ordinary records — list them through the repository API for `stowbo_listing`. |

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

#### See also

- `GET /stowbo/addons`

### 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. |
| `listing` | path | string | yes | Listing `name`. A site aggregates its child listings; a leaf listing stands alone. |

### Responses

| Status | Meaning |
| --- | --- |
| `200` | Resolved add-ons |
| `401` | Authentication failed: Invalid or expired token — The `Authorization` header is missing, malformed, or the JWT has expired. |
| `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. |
| `404` | Listing <name> not found — No listing in the org has that name or 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 /stowbo/listing/{listing}/capacity

**Cap a space's capacity for a window**

`operationId: StowboController_capCapacity`

A blackout with a number: the space stays open but takes at most `capacity` for the window. `capacity: 0` closes it. The window defaults to the next 24 hours from now. Bookings already inside it come back as `conflicts`, not cancelled.

#### Signature

```http
POST /stowbo/listing/{listing}/capacity (listing: string, body) -> The closure, all closures, and bookings caught inside it — as `POST /stowbo/listing/{listing}/blackouts`
```

#### Access

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

#### Errors

| Status | Code | Message | When | What to do |
| --- | --- | --- | --- | --- |
| `404` | LISTING_NOT_FOUND | Listing <name> not found | No listing in the org has that name or id. | Listings are ordinary records — list them through the repository API for `stowbo_listing`. |
| `400` | INVALID_WINDOW | to must be after from | `to` is at or before `from`. | — |

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

#### See also

- `POST /stowbo/listing/{listing}/blackouts`

### 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. |
| `listing` | path | string | yes | Listing `name`. A site aggregates its child listings; a leaf listing stands alone. |

### Request body

```json
{
  "capacity": 5,
  "from": "2026-10-01T00:00:00.000Z",
  "to": "2026-10-03T00:00:00.000Z",
  "reason": "event"
}
```

### Responses

| Status | Meaning |
| --- | --- |
| `201` | The closure, all closures, and bookings caught inside it — as `POST /stowbo/listing/{listing}/blackouts` |
| `400` | to must be after from — `to` is at or before `from`. |
| `401` | Authentication failed: Invalid or expired token — The `Authorization` header is missing, malformed, or the JWT has expired. |
| `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. |
| `404` | Listing <name> not found — No listing in the org has that name or 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. |

