# Storefront · Rentals

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

**Get rental configuration**

`operationId: RentalController_getConfig`

Returns the org's rental settings — periods, fees and defaults. Omit `name` for the default configuration.

#### Signature

```http
GET /storefront/rental/config (name?: string) -> The rental configuration
```

#### Access

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

#### Errors

| Status | Code | Message | When | What to do |
| --- | --- | --- | --- | --- |
| `500` | INTERNAL | The underlying error message, passed through verbatim. | An unexpected failure inside the rental service. | Unlike most of the platform, this surfaces the raw internal message rather than a generic one. Treat the text as a debugging aid, not a stable contract. |

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

#### See also

- `GET /storefront/rental/items`

### 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. |
| `name` | query | string | — | Named configuration. Omit for the default. |

### Responses

| Status | Meaning |
| --- | --- |
| `200` | The rental configuration |
| `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. |
| `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` | The underlying error message, passed through verbatim. — An unexpected failure inside the rental service. |

## GET /storefront/rental/items

**List rental items**

`operationId: RentalController_listRentalItems`

Lists the items available to rent, with optional status filtering and paging. Items in `maintenance` are not rentable but still listed.

#### Signature

```http
GET /storefront/rental/items (status?: string, page?: integer, pageSize?: integer) -> A page of rental items
```

#### Access

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

#### Errors

| Status | Code | Message | When | What to do |
| --- | --- | --- | --- | --- |
| `500` | INTERNAL | The underlying error message, passed through verbatim. | An unexpected failure inside the rental service. | Unlike most of the platform, this surfaces the raw internal message rather than a generic one. Treat the text as a debugging aid, not a stable contract. |

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

#### See also

- `GET /storefront/rental/items/{sku}`

### 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. |
| `status` | query | "active" \| "inactive" \| "maintenance" | — |  |
| `page` | query | integer | — |  |
| `pageSize` | query | integer | — |  |

### Responses

| Status | Meaning |
| --- | --- |
| `200` | A page of rental items |
| `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. |
| `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` | The underlying error message, passed through verbatim. — An unexpected failure inside the rental service. |

## GET /storefront/rental/items/{sku}

**Get a rental item**

`operationId: RentalController_getRentalItem`

Fetches one rentable item by SKU, including its rates, deposit and any customer restrictions.

#### Signature

```http
GET /storefront/rental/items/{sku} (sku: string) -> The rental item
```

#### Access

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

#### Errors

| Status | Code | Message | When | What to do |
| --- | --- | --- | --- | --- |
| `404` | RENTAL_ITEM_NOT_FOUND | Rental item not found | The SKU does not resolve to a rental item. | List rentable items with `GET /storefront/rental/items`. |
| `500` | INTERNAL | The underlying error message, passed through verbatim. | An unexpected failure inside the rental service. | Unlike most of the platform, this surfaces the raw internal message rather than a generic one. Treat the text as a debugging aid, not a stable contract. |

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

#### See also

- `GET /storefront/rental/items/{sku}/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. |
| `sku` | path | string | yes | Rental item SKU. |

### Responses

| Status | Meaning |
| --- | --- |
| `200` | The rental item |
| `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` | Rental item not found — The SKU does not resolve to a rental item. |
| `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` | The underlying error message, passed through verbatim. — An unexpected failure inside the rental service. |

## GET /storefront/rental/items/{sku}/availability

**Check rental availability**

`operationId: RentalController_checkAvailability`

Reports whether an item is free for a date range, accounting for existing bookings.

Availability is checked again when the rental is created, so a positive answer here is not a reservation — another customer can still book the window first.

#### Signature

```http
GET /storefront/rental/items/{sku}/availability (sku: string, startDate?: string, endDate?: string, quantity?: integer) -> Availability for the window
```

#### Access

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

#### Notes

- Not a hold. The authoritative check happens at `POST /storefront/rental`, which returns `409` if the window has gone.

#### Errors

| Status | Code | Message | When | What to do |
| --- | --- | --- | --- | --- |
| `404` | RENTAL_ITEM_NOT_FOUND | Rental item not found | The SKU does not resolve to a rental item. | List rentable items with `GET /storefront/rental/items`. |
| `500` | INTERNAL | The underlying error message, passed through verbatim. | An unexpected failure inside the rental service. | Unlike most of the platform, this surfaces the raw internal message rather than a generic one. Treat the text as a debugging aid, not a stable contract. |

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

#### See also

- `POST /storefront/rental/items/{sku}/price`
- `POST /storefront/rental`

### 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. |
| `sku` | path | string | yes | Rental item SKU. |
| `startDate` | query | string | yes | Start of the window, ISO 8601. |
| `endDate` | query | string | yes | End of the window, ISO 8601. |
| `quantity` | query | integer | — |  |

### Responses

| Status | Meaning |
| --- | --- |
| `200` | Availability for the window |
| `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` | Rental item not found — The SKU does not resolve to a rental item. |
| `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` | The underlying error message, passed through verbatim. — An unexpected failure inside the rental service. |

## POST /storefront/rental/items/{sku}/price

**Calculate a rental price**

`operationId: RentalController_calculatePrice`

Prices a rental for a date range, including any optional fees the customer selects — insurance, cleaning, delivery. The rate band is chosen from the length of the window, so a week may cost less than seven days.

#### Signature

```http
POST /storefront/rental/items/{sku}/price (sku: string, body) -> The priced rental
```

#### Access

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

#### Errors

| Status | Code | Message | When | What to do |
| --- | --- | --- | --- | --- |
| `404` | RENTAL_ITEM_NOT_FOUND | Rental item not found | The SKU does not resolve to a rental item. | List rentable items with `GET /storefront/rental/items`. |
| `500` | INTERNAL | The underlying error message, passed through verbatim. | An unexpected failure inside the rental service. | Unlike most of the platform, this surfaces the raw internal message rather than a generic one. Treat the text as a debugging aid, not a stable contract. |

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

#### See also

- `POST /storefront/rental`

### 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. |
| `sku` | path | string | yes | Rental item SKU. |

### Request body

The window and options to price.

```json
{
  "startDate": "2026-09-01T09:00:00.000Z",
  "endDate": "2026-09-04T17:00:00.000Z",
  "quantity": 1,
  "optionalFees": [
    "insurance"
  ]
}
```

### Responses

| Status | Meaning |
| --- | --- |
| `201` | The priced rental |
| `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` | Rental item not found — The SKU does not resolve to a rental item. |
| `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` | The underlying error message, passed through verbatim. — An unexpected failure inside the rental service. |

## GET /storefront/rental

**List rentals**

`operationId: RentalController_listRentals`

Lists rental bookings with filters and paging. Filter by customer, item, status or start-date range.

#### Signature

```http
GET /storefront/rental (status?: string, customerEmail?: string, sku?: string, startDateFrom?: string, startDateTo?: string, page?: integer, pageSize?: integer) -> A page of rentals
```

#### Access

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

#### Errors

| Status | Code | Message | When | What to do |
| --- | --- | --- | --- | --- |
| `500` | INTERNAL | The underlying error message, passed through verbatim. | An unexpected failure inside the rental service. | Unlike most of the platform, this surfaces the raw internal message rather than a generic one. Treat the text as a debugging aid, not a stable contract. |

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

#### See also

- `GET /storefront/rental/status/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. |
| `status` | query | "pending" \| "confirmed" \| "ready" \| "picked_up" \| "delivered" \| "active" \| "overdue" \| "dropped_off" \| "returned" \| "completed" \| "cancelled" | — |  |
| `customerEmail` | query | string | — |  |
| `sku` | query | string | — |  |
| `startDateFrom` | query | string | — | Lower bound on start date. |
| `startDateTo` | query | string | — | Upper bound on start date. |
| `page` | query | integer | — |  |
| `pageSize` | query | integer | — |  |

### Responses

| Status | Meaning |
| --- | --- |
| `200` | A page of rentals |
| `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. |
| `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` | The underlying error message, passed through verbatim. — An unexpected failure inside the rental service. |

## POST /storefront/rental

**Create a rental booking**

`operationId: RentalController_createRental`

Books an item for a date range. The rental starts `pending`.

Three checks run in order, and each has its own failure: the item must exist, it must be free for the window, and the customer must satisfy the item's restrictions. **Availability is re-checked here**, so a window that was free when you queried it can still be lost to another booking — a `409` means exactly that.

Fulfilment and return are described separately, so an item can be delivered and collected, picked up and dropped off, or any combination.

#### Signature

```http
POST /storefront/rental (body) -> The created rental
```

#### Access

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

#### Notes

- The `403` body carries an `errors` array naming each failed restriction, rather than the standard error envelope.

#### Errors

| Status | Code | Message | When | What to do |
| --- | --- | --- | --- | --- |
| `404` | RENTAL_ITEM_NOT_FOUND | Rental item not found | The SKU does not resolve to a rental item. | List rentable items with `GET /storefront/rental/items`. |
| `409` | NOT_AVAILABLE | Item not available for selected dates | The item is already booked for some part of the window. | Re-check with the availability endpoint and offer the customer another window. Availability is not held between checking and booking. |
| `403` | RESTRICTIONS_NOT_MET | Customer restrictions not met | The customer fails one of the item's restrictions — age or location, typically. | Validate first with `POST /storefront/rental/items/{sku}/validate-restrictions`, which reports every failure at once. |
| `500` | INTERNAL | The underlying error message, passed through verbatim. | An unexpected failure inside the rental service. | Unlike most of the platform, this surfaces the raw internal message rather than a generic one. Treat the text as a debugging aid, not a stable contract. |

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

#### See also

- `GET /storefront/rental/items/{sku}/availability`
- `PUT /storefront/rental/{rentalId}/confirm`

### 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, window, customer and logistics.

```json
{
  "sku": "CAM-RED-KOMODO",
  "customer": {
    "firstName": "Ada",
    "lastName": "Lovelace",
    "email": "ada@example.com",
    "phone": "+15551234567"
  },
  "startDate": "2026-09-01T09:00:00.000Z",
  "endDate": "2026-09-04T17:00:00.000Z",
  "fulfillment": {
    "type": "pickup"
  },
  "return": {
    "type": "dropoff"
  }
}
```

### Responses

| Status | Meaning |
| --- | --- |
| `201` | The created rental |
| `401` | Authentication failed: Invalid or expired token — The `Authorization` header is missing, malformed, or the JWT has expired. |
| `403` | Customer restrictions not met — The customer fails one of the item's restrictions — age or location, typically. |
| `404` | Rental item not found — The SKU does not resolve to a rental item. |
| `409` | Item not available for selected dates — The item is already booked for some part of the window. |
| `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` | The underlying error message, passed through verbatim. — An unexpected failure inside the rental service. |

## GET /storefront/rental/{rentalId}

**Get a rental**

`operationId: RentalController_getRental`

Fetches one rental with its window, customer, deposit state and both condition records.

#### Signature

```http
GET /storefront/rental/{rentalId} (rentalId: string) -> The rental
```

#### Access

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

#### Errors

| Status | Code | Message | When | What to do |
| --- | --- | --- | --- | --- |
| `404` | RENTAL_NOT_FOUND | Rental not found | No rental in the org has that id. | Check the id with `GET /storefront/rental`. |
| `500` | INTERNAL | The underlying error message, passed through verbatim. | An unexpected failure inside the rental service. | Unlike most of the platform, this surfaces the raw internal message rather than a generic one. Treat the text as a debugging aid, not a stable contract. |

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

#### See also

- `GET /storefront/rental`

### 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. |
| `rentalId` | path | string | yes | Rental id. |

### Responses

| Status | Meaning |
| --- | --- |
| `200` | The rental |
| `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` | Rental not found — No rental in the org 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` | The underlying error message, passed through verbatim. — An unexpected failure inside the rental service. |

## GET /storefront/rental/status/overdue

**Get overdue rentals**

`operationId: RentalController_getOverdueRentals`

Every active rental past its end date and not yet returned. The chase list.

#### Signature

```http
GET /storefront/rental/status/overdue () -> Overdue rentals
```

#### Access

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

#### Notes

- Derived from the end date at request time. Marking a rental `overdue` with the status endpoint is a separate, manual flag.

#### Errors

| Status | Code | Message | When | What to do |
| --- | --- | --- | --- | --- |
| `500` | INTERNAL | The underlying error message, passed through verbatim. | An unexpected failure inside the rental service. | Unlike most of the platform, this surfaces the raw internal message rather than a generic one. Treat the text as a debugging aid, not a stable contract. |

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

#### See also

- `GET /storefront/rental/status/due-today`
- `PUT /storefront/rental/{rentalId}/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. |

### Responses

| Status | Meaning |
| --- | --- |
| `200` | Overdue rentals |
| `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. |
| `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` | The underlying error message, passed through verbatim. — An unexpected failure inside the rental service. |

## GET /storefront/rental/status/due-today

**Get rentals due today**

`operationId: RentalController_getRentalsDueToday`

Rentals whose window ends today — what a counter expects back before closing.

#### Signature

```http
GET /storefront/rental/status/due-today () -> Rentals ending today
```

#### Access

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

#### Errors

| Status | Code | Message | When | What to do |
| --- | --- | --- | --- | --- |
| `500` | INTERNAL | The underlying error message, passed through verbatim. | An unexpected failure inside the rental service. | Unlike most of the platform, this surfaces the raw internal message rather than a generic one. Treat the text as a debugging aid, not a stable contract. |

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

#### See also

- `GET /storefront/rental/status/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. |

### Responses

| Status | Meaning |
| --- | --- |
| `200` | Rentals ending today |
| `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. |
| `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` | The underlying error message, passed through verbatim. — An unexpected failure inside the rental service. |

## PUT /storefront/rental/{rentalId}/confirm

**Confirm a rental**

`operationId: RentalController_confirmRental`

Moves a pending rental to `confirmed`, committing the booking. This is the point the customer is told the item is theirs for the window.

#### Signature

```http
PUT /storefront/rental/{rentalId}/confirm (rentalId: string) -> The updated rental
```

#### Access

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

#### Errors

| Status | Code | Message | When | What to do |
| --- | --- | --- | --- | --- |
| `404` | RENTAL_NOT_FOUND | Rental not found | No rental in the org has that id. | Check the id with `GET /storefront/rental`. |
| `500` | INTERNAL | The underlying error message, passed through verbatim. | An unexpected failure inside the rental service. | Unlike most of the platform, this surfaces the raw internal message rather than a generic one. Treat the text as a debugging aid, not a stable contract. |

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

#### See also

- `PUT /storefront/rental/{rentalId}/ready`

### 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. |
| `rentalId` | path | string | yes | Rental id. |

### Responses

| Status | Meaning |
| --- | --- |
| `200` | The updated rental |
| `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` | Rental not found — No rental in the org 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` | The underlying error message, passed through verbatim. — An unexpected failure inside the rental service. |

## PUT /storefront/rental/{rentalId}/checkout

**Record check-out condition**

`operationId: RentalController_checkOut`

Records the item's condition as it leaves, with optional photographs and the name of whoever inspected it.

This is the **before** half of the evidence a deposit deduction rests on. Without it there is nothing to compare the returned condition against, and a damage claim comes down to one party's word.

#### Signature

```http
PUT /storefront/rental/{rentalId}/checkout (rentalId: string, body) -> The rental with its check-out record
```

#### Access

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

#### Errors

| Status | Code | Message | When | What to do |
| --- | --- | --- | --- | --- |
| `404` | RENTAL_NOT_FOUND | Rental not found | No rental in the org has that id. | Check the id with `GET /storefront/rental`. |
| `500` | INTERNAL | The underlying error message, passed through verbatim. | An unexpected failure inside the rental service. | Unlike most of the platform, this surfaces the raw internal message rather than a generic one. Treat the text as a debugging aid, not a stable contract. |

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

#### See also

- `PUT /storefront/rental/{rentalId}/checkin`

### 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. |
| `rentalId` | path | string | yes | Rental id. |

### Request body

Condition at the point the item leaves.

```json
{
  "condition": "excellent",
  "notes": "Body and lens both clean, no marks",
  "photos": [
    "https://cdn.appmint.io/rentals/rnt-4821-out-1.jpg"
  ],
  "checkedBy": "counter@venue.com"
}
```

### Responses

| Status | Meaning |
| --- | --- |
| `200` | The rental with its check-out record |
| `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` | Rental not found — No rental in the org 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` | The underlying error message, passed through verbatim. — An unexpected failure inside the rental service. |

## PUT /storefront/rental/{rentalId}/checkin

**Record check-in condition**

`operationId: RentalController_checkIn`

Records the item's condition on return, including any damage found. The **after** half of the evidence.

Recording `damages` here does not itself charge anything — add a deposit deduction to do that, and refund the remainder.

#### Signature

```http
PUT /storefront/rental/{rentalId}/checkin (rentalId: string, body) -> The rental with its check-in record
```

#### Access

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

#### Notes

- `damages` is descriptive only. Charge for it with `POST /storefront/rental/{rentalId}/deposit/deduction`.

#### Errors

| Status | Code | Message | When | What to do |
| --- | --- | --- | --- | --- |
| `404` | RENTAL_NOT_FOUND | Rental not found | No rental in the org has that id. | Check the id with `GET /storefront/rental`. |
| `500` | INTERNAL | The underlying error message, passed through verbatim. | An unexpected failure inside the rental service. | Unlike most of the platform, this surfaces the raw internal message rather than a generic one. Treat the text as a debugging aid, not a stable contract. |

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

#### See also

- `POST /storefront/rental/{rentalId}/deposit/deduction`

### 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. |
| `rentalId` | path | string | yes | Rental id. |

### Request body

Condition at the point the item comes back.

```json
{
  "condition": "fair",
  "notes": "Scratched lens housing",
  "damages": "Scratched lens housing",
  "photos": [
    "https://cdn.appmint.io/rentals/rnt-4821-in-1.jpg"
  ],
  "checkedBy": "counter@venue.com"
}
```

### Responses

| Status | Meaning |
| --- | --- |
| `200` | The rental with its check-in record |
| `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` | Rental not found — No rental in the org 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` | The underlying error message, passed through verbatim. — An unexpected failure inside the rental service. |

## PUT /storefront/rental/{rentalId}/complete

**Complete a rental**

`operationId: RentalController_completeRental`

Closes the rental. Settle the deposit first — refund what is owed and record any deductions — because completion is the end of the lifecycle.

#### Signature

```http
PUT /storefront/rental/{rentalId}/complete (rentalId: string) -> The updated rental
```

#### Access

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

#### Errors

| Status | Code | Message | When | What to do |
| --- | --- | --- | --- | --- |
| `404` | RENTAL_NOT_FOUND | Rental not found | No rental in the org has that id. | Check the id with `GET /storefront/rental`. |
| `500` | INTERNAL | The underlying error message, passed through verbatim. | An unexpected failure inside the rental service. | Unlike most of the platform, this surfaces the raw internal message rather than a generic one. Treat the text as a debugging aid, not a stable contract. |

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

#### See also

- `PUT /storefront/rental/{rentalId}/deposit/refund`

### 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. |
| `rentalId` | path | string | yes | Rental id. |

### Responses

| Status | Meaning |
| --- | --- |
| `200` | The updated rental |
| `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` | Rental not found — No rental in the org 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` | The underlying error message, passed through verbatim. — An unexpected failure inside the rental service. |

## PUT /storefront/rental/{rentalId}/cancel

**Cancel a rental**

`operationId: RentalController_cancelRental`

Cancels a rental, recording the reason. Any deposit that was held must be refunded separately — cancelling does not release it.

#### Signature

```http
PUT /storefront/rental/{rentalId}/cancel (rentalId: string, body) -> The cancelled rental
```

#### Access

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

#### Notes

- Does not refund the deposit or any payment — issue those explicitly.

#### Errors

| Status | Code | Message | When | What to do |
| --- | --- | --- | --- | --- |
| `404` | RENTAL_NOT_FOUND | Rental not found | No rental in the org has that id. | Check the id with `GET /storefront/rental`. |
| `500` | INTERNAL | The underlying error message, passed through verbatim. | An unexpected failure inside the rental service. | Unlike most of the platform, this surfaces the raw internal message rather than a generic one. Treat the text as a debugging aid, not a stable contract. |

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

#### See also

- `PUT /storefront/rental/{rentalId}/deposit/refund`

### 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. |
| `rentalId` | path | string | yes | Rental id. |

### Request body

Why the rental is being cancelled.

```json
{
  "reason": "Customer cancelled — shoot postponed"
}
```

### Responses

| Status | Meaning |
| --- | --- |
| `200` | The cancelled rental |
| `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` | Rental not found — No rental in the org 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` | The underlying error message, passed through verbatim. — An unexpected failure inside the rental service. |

## PUT /storefront/rental/{rentalId}/ready

**Mark a rental ready**

`operationId: RentalController_markReady`

Marks the item prepared and waiting for collection or dispatch. The signal to a counter that it can be handed over.

#### Signature

```http
PUT /storefront/rental/{rentalId}/ready (rentalId: string) -> The updated rental
```

#### Access

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

#### Errors

| Status | Code | Message | When | What to do |
| --- | --- | --- | --- | --- |
| `404` | RENTAL_NOT_FOUND | Rental not found | No rental in the org has that id. | Check the id with `GET /storefront/rental`. |
| `500` | INTERNAL | The underlying error message, passed through verbatim. | An unexpected failure inside the rental service. | Unlike most of the platform, this surfaces the raw internal message rather than a generic one. Treat the text as a debugging aid, not a stable contract. |

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

#### See also

- `PUT /storefront/rental/{rentalId}/picked-up`
- `PUT /storefront/rental/{rentalId}/delivered`

### 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. |
| `rentalId` | path | string | yes | Rental id. |

### Responses

| Status | Meaning |
| --- | --- |
| `200` | The updated rental |
| `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` | Rental not found — No rental in the org 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` | The underlying error message, passed through verbatim. — An unexpected failure inside the rental service. |

## PUT /storefront/rental/{rentalId}/picked-up

**Mark a rental picked up**

`operationId: RentalController_markPickedUp`

Records that the customer collected the item in person, and notifies them. Use `delivered` instead when the item was sent out.

#### Signature

```http
PUT /storefront/rental/{rentalId}/picked-up (rentalId: string) -> The updated rental
```

#### Access

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

#### Errors

| Status | Code | Message | When | What to do |
| --- | --- | --- | --- | --- |
| `404` | RENTAL_NOT_FOUND | Rental not found | No rental in the org has that id. | Check the id with `GET /storefront/rental`. |
| `500` | INTERNAL | The underlying error message, passed through verbatim. | An unexpected failure inside the rental service. | Unlike most of the platform, this surfaces the raw internal message rather than a generic one. Treat the text as a debugging aid, not a stable contract. |

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

#### See also

- `PUT /storefront/rental/{rentalId}/checkout`
- `PUT /storefront/rental/{rentalId}/delivered`

### 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. |
| `rentalId` | path | string | yes | Rental id. |

### Responses

| Status | Meaning |
| --- | --- |
| `200` | The updated rental |
| `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` | Rental not found — No rental in the org 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` | The underlying error message, passed through verbatim. — An unexpected failure inside the rental service. |

## PUT /storefront/rental/{rentalId}/delivered

**Mark a rental delivered**

`operationId: RentalController_markDelivered`

Records that the item was delivered to the customer, and notifies them. The delivery counterpart to `picked-up`.

#### Signature

```http
PUT /storefront/rental/{rentalId}/delivered (rentalId: string) -> The updated rental
```

#### Access

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

#### Errors

| Status | Code | Message | When | What to do |
| --- | --- | --- | --- | --- |
| `404` | RENTAL_NOT_FOUND | Rental not found | No rental in the org has that id. | Check the id with `GET /storefront/rental`. |
| `500` | INTERNAL | The underlying error message, passed through verbatim. | An unexpected failure inside the rental service. | Unlike most of the platform, this surfaces the raw internal message rather than a generic one. Treat the text as a debugging aid, not a stable contract. |

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

#### See also

- `PUT /storefront/rental/{rentalId}/picked-up`

### 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. |
| `rentalId` | path | string | yes | Rental id. |

### Responses

| Status | Meaning |
| --- | --- |
| `200` | The updated rental |
| `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` | Rental not found — No rental in the org 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` | The underlying error message, passed through verbatim. — An unexpected failure inside the rental service. |

## PUT /storefront/rental/{rentalId}/active

**Mark a rental active**

`operationId: RentalController_markActive`

Marks the item as in use by the customer — the state a rental sits in for the body of its window.

#### Signature

```http
PUT /storefront/rental/{rentalId}/active (rentalId: string) -> The updated rental
```

#### Access

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

#### Errors

| Status | Code | Message | When | What to do |
| --- | --- | --- | --- | --- |
| `404` | RENTAL_NOT_FOUND | Rental not found | No rental in the org has that id. | Check the id with `GET /storefront/rental`. |
| `500` | INTERNAL | The underlying error message, passed through verbatim. | An unexpected failure inside the rental service. | Unlike most of the platform, this surfaces the raw internal message rather than a generic one. Treat the text as a debugging aid, not a stable contract. |

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

#### See also

- `PUT /storefront/rental/{rentalId}/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. |
| `rentalId` | path | string | yes | Rental id. |

### Responses

| Status | Meaning |
| --- | --- |
| `200` | The updated rental |
| `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` | Rental not found — No rental in the org 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` | The underlying error message, passed through verbatim. — An unexpected failure inside the rental service. |

## PUT /storefront/rental/{rentalId}/overdue

**Mark a rental overdue**

`operationId: RentalController_markOverdue`

Flags a rental as overdue. This is a **manual** status change; `GET /storefront/rental/status/overdue` derives its list from dates instead and does not depend on this flag being set.

#### Signature

```http
PUT /storefront/rental/{rentalId}/overdue (rentalId: string) -> The updated rental
```

#### Access

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

#### Errors

| Status | Code | Message | When | What to do |
| --- | --- | --- | --- | --- |
| `404` | RENTAL_NOT_FOUND | Rental not found | No rental in the org has that id. | Check the id with `GET /storefront/rental`. |
| `500` | INTERNAL | The underlying error message, passed through verbatim. | An unexpected failure inside the rental service. | Unlike most of the platform, this surfaces the raw internal message rather than a generic one. Treat the text as a debugging aid, not a stable contract. |

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

#### See also

- `GET /storefront/rental/status/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. |
| `rentalId` | path | string | yes | Rental id. |

### Responses

| Status | Meaning |
| --- | --- |
| `200` | The updated rental |
| `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` | Rental not found — No rental in the org 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` | The underlying error message, passed through verbatim. — An unexpected failure inside the rental service. |

## PUT /storefront/rental/{rentalId}/dropped-off

**Mark a rental dropped off**

`operationId: RentalController_markDroppedOff`

Records that the customer returned the item, and notifies them. The item is back but not yet inspected — record the condition with `checkin`.

#### Signature

```http
PUT /storefront/rental/{rentalId}/dropped-off (rentalId: string) -> The updated rental
```

#### Access

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

#### Errors

| Status | Code | Message | When | What to do |
| --- | --- | --- | --- | --- |
| `404` | RENTAL_NOT_FOUND | Rental not found | No rental in the org has that id. | Check the id with `GET /storefront/rental`. |
| `500` | INTERNAL | The underlying error message, passed through verbatim. | An unexpected failure inside the rental service. | Unlike most of the platform, this surfaces the raw internal message rather than a generic one. Treat the text as a debugging aid, not a stable contract. |

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

#### See also

- `PUT /storefront/rental/{rentalId}/checkin`

### 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. |
| `rentalId` | path | string | yes | Rental id. |

### Responses

| Status | Meaning |
| --- | --- |
| `200` | The updated rental |
| `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` | Rental not found — No rental in the org 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` | The underlying error message, passed through verbatim. — An unexpected failure inside the rental service. |

## PUT /storefront/rental/{rentalId}/returned

**Mark a rental returned**

> **Deprecated.**

`operationId: RentalController_markReturned`

> **Deprecated.** Superseded by `PUT /storefront/rental/{rentalId}/dropped-off`, which it delegates to.

Alias for `dropped-off`, kept for older clients. Prefer `PUT /storefront/rental/{rentalId}/dropped-off` in new work.

#### Signature

```http
PUT /storefront/rental/{rentalId}/returned (rentalId: string) -> The updated rental
```

#### Access

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

#### Errors

| Status | Code | Message | When | What to do |
| --- | --- | --- | --- | --- |
| `404` | RENTAL_NOT_FOUND | Rental not found | No rental in the org has that id. | Check the id with `GET /storefront/rental`. |
| `500` | INTERNAL | The underlying error message, passed through verbatim. | An unexpected failure inside the rental service. | Unlike most of the platform, this surfaces the raw internal message rather than a generic one. Treat the text as a debugging aid, not a stable contract. |

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

#### See also

- `PUT /storefront/rental/{rentalId}/dropped-off`

### 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. |
| `rentalId` | path | string | yes | Rental id. |

### Responses

| Status | Meaning |
| --- | --- |
| `200` | The updated rental |
| `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` | Rental not found — No rental in the org 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` | The underlying error message, passed through verbatim. — An unexpected failure inside the rental service. |

## PUT /storefront/rental/{rentalId}/notes

**Update internal notes**

`operationId: RentalController_updateNotes`

Replaces the rental's internal notes. The status is left unchanged.

**A missing rental is reported oddly here:** the handler returns `{ "error": "Rental not found" }` with a `200`, rather than the `404` every other rental endpoint raises. Check the body, not the status.

#### Signature

```http
PUT /storefront/rental/{rentalId}/notes (rentalId: string, body) -> The updated rental, or an error object when the rental does not exist
```

#### Access

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

#### Notes

- Replaces the notes rather than appending — read them first if you want to add to them.
- Inconsistent with the rest of the controller: no `404` is raised for a missing rental.

#### Errors

| Status | Code | Message | When | What to do |
| --- | --- | --- | --- | --- |
| `500` | INTERNAL | The underlying error message, passed through verbatim. | An unexpected failure inside the rental service. | Unlike most of the platform, this surfaces the raw internal message rather than a generic one. Treat the text as a debugging aid, not a stable contract. |

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

#### See also

- `GET /storefront/rental/{rentalId}`

### 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. |
| `rentalId` | path | string | yes | Rental id. |

### Request body

The notes to store.

```json
{
  "notes": "Customer is a regular — waive the late fee if under an hour"
}
```

### Responses

| Status | Meaning |
| --- | --- |
| `200` | The updated rental, or an error object when the rental does not exist |
| `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. |
| `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` | The underlying error message, passed through verbatim. — An unexpected failure inside the rental service. |

Example response:

```json
{
  "data": {
    "rentalId": "RNT-4821",
    "internalNotes": "Customer is a regular"
  }
}
```

## POST /storefront/rental/{rentalId}/payment

**Record a rental payment**

`operationId: RentalController_recordPayment`

Records a payment against the rental. This books the tender; it does not charge a card — take the payment through a gateway first and record its reference here.

#### Signature

```http
POST /storefront/rental/{rentalId}/payment (rentalId: string, body) -> The updated rental
```

#### Access

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

#### Notes

- Not idempotent — a retry records a second payment.

#### Errors

| Status | Code | Message | When | What to do |
| --- | --- | --- | --- | --- |
| `404` | RENTAL_NOT_FOUND | Rental not found | No rental in the org has that id. | Check the id with `GET /storefront/rental`. |
| `500` | INTERNAL | The underlying error message, passed through verbatim. | An unexpected failure inside the rental service. | Unlike most of the platform, this surfaces the raw internal message rather than a generic one. Treat the text as a debugging aid, not a stable contract. |

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

#### See also

- `PUT /storefront/rental/{rentalId}/deposit/hold`

### 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. |
| `rentalId` | path | string | yes | Rental id. |

### Request body

The payment to record.

```json
{
  "amount": 495,
  "method": "card",
  "transactionId": "pi_3PabcXYZ"
}
```

### Responses

| Status | Meaning |
| --- | --- |
| `201` | The updated rental |
| `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` | Rental not found — No rental in the org 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` | The underlying error message, passed through verbatim. — An unexpected failure inside the rental service. |

## PUT /storefront/rental/{rentalId}/deposit/hold

**Record a deposit hold**

`operationId: RentalController_holdDeposit`

Records that a deposit has been held, capturing the gateway references needed to refund it later.

**`transactionId` is what a later refund is issued against.** Without it the refund has no gateway reference to reverse and can only be recorded as a manual adjustment, so capture it at hold time rather than trying to reconstruct it at the end of the rental.

#### Signature

```http
PUT /storefront/rental/{rentalId}/deposit/hold (rentalId: string, body) -> The rental with its deposit hold recorded
```

#### Access

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

#### Errors

| Status | Code | Message | When | What to do |
| --- | --- | --- | --- | --- |
| `404` | RENTAL_NOT_FOUND | Rental not found | No rental in the org has that id. | Check the id with `GET /storefront/rental`. |
| `500` | INTERNAL | The underlying error message, passed through verbatim. | An unexpected failure inside the rental service. | Unlike most of the platform, this surfaces the raw internal message rather than a generic one. Treat the text as a debugging aid, not a stable contract. |

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

#### See also

- `PUT /storefront/rental/{rentalId}/deposit/refund`

### 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. |
| `rentalId` | path | string | yes | Rental id. |

### Request body

The gateway references for the held deposit.

```json
{
  "transactionId": "pi_3PabcXYZ",
  "chargeId": "ch_3PabcXYZ",
  "paymentGateway": "stripe"
}
```

### Responses

| Status | Meaning |
| --- | --- |
| `200` | The rental with its deposit hold recorded |
| `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` | Rental not found — No rental in the org 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` | The underlying error message, passed through verbatim. — An unexpected failure inside the rental service. |

## PUT /storefront/rental/{rentalId}/deposit/refund

**Refund the deposit**

`operationId: RentalController_refundDeposit`

Returns deposit money to the customer through the **original payment gateway**, using the references captured when the deposit was held.

Set `skipPaymentGateway` when the money has already been returned by other means — cash at the counter, or a refund issued directly in the provider's dashboard. That records the refund without attempting to move money a second time.

#### Signature

```http
PUT /storefront/rental/{rentalId}/deposit/refund (rentalId: string, body) -> The rental with the refund recorded
```

#### Access

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

#### Notes

- Needs the references stored by `deposit/hold`. Without them, use `skipPaymentGateway` and settle outside the platform.
- The amount is not derived from the deductions — calculate it yourself, or you may refund more than is owed.

#### Errors

| Status | Code | Message | When | What to do |
| --- | --- | --- | --- | --- |
| `404` | RENTAL_NOT_FOUND | Rental not found | No rental in the org has that id. | Check the id with `GET /storefront/rental`. |
| `500` | INTERNAL | The underlying error message, passed through verbatim. | An unexpected failure inside the rental service. | Unlike most of the platform, this surfaces the raw internal message rather than a generic one. Treat the text as a debugging aid, not a stable contract. |

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

#### See also

- `PUT /storefront/rental/{rentalId}/deposit/hold`
- `POST /storefront/rental/{rentalId}/deposit/deduction`

### 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. |
| `rentalId` | path | string | yes | Rental id. |

### Request body

How much to return, and whether to involve the gateway.

```json
{
  "amount": 175,
  "reason": "Deposit returned less damage deduction"
}
```

### Responses

| Status | Meaning |
| --- | --- |
| `200` | The rental with the refund recorded |
| `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` | Rental not found — No rental in the org 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` | The underlying error message, passed through verbatim. — An unexpected failure inside the rental service. |

## POST /storefront/rental/{rentalId}/deposit/deduction

**Deduct from the deposit**

`operationId: RentalController_addDepositDeduction`

Records a deduction against the deposit for damage, late return, cleaning, or anything else the customer is liable for. Each deduction carries its own reason, so the customer can be shown an itemised account.

Deductions reduce what will be refunded; they do not move money on their own. Refund the remainder explicitly once the total is settled.

#### Signature

```http
POST /storefront/rental/{rentalId}/deposit/deduction (rentalId: string, body) -> The rental with the deduction recorded
```

#### Access

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

#### Notes

- Call once per distinct charge — several deductions produce an itemised list rather than one opaque total.
- Nothing checks the total of deductions against the deposit, so it is possible to record more than was held.

#### Errors

| Status | Code | Message | When | What to do |
| --- | --- | --- | --- | --- |
| `404` | RENTAL_NOT_FOUND | Rental not found | No rental in the org has that id. | Check the id with `GET /storefront/rental`. |
| `500` | INTERNAL | The underlying error message, passed through verbatim. | An unexpected failure inside the rental service. | Unlike most of the platform, this surfaces the raw internal message rather than a generic one. Treat the text as a debugging aid, not a stable contract. |

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

#### See also

- `PUT /storefront/rental/{rentalId}/checkin`
- `PUT /storefront/rental/{rentalId}/deposit/refund`

### 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. |
| `rentalId` | path | string | yes | Rental id. |

### Request body

What is being deducted, and why.

```json
{
  "reason": "Scratched lens housing",
  "amount": 75
}
```

### Responses

| Status | Meaning |
| --- | --- |
| `201` | The rental with the deduction recorded |
| `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` | Rental not found — No rental in the org 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` | The underlying error message, passed through verbatim. — An unexpected failure inside the rental service. |

## POST /storefront/rental/items/{sku}/validate-restrictions

**Validate customer restrictions**

`operationId: RentalController_validateRestrictions`

Checks whether a customer may rent an item — age limits, geographic restrictions, and anything else the item defines.

A failure is reported as `valid: false` with a list of what failed, **not** as an error status, so the booking form can show every problem at once. Call this before taking payment: the same rules are enforced at creation, where they produce a `403` instead.

#### Signature

```http
POST /storefront/rental/items/{sku}/validate-restrictions (sku: string, body) -> Whether the customer qualifies, and what failed if not
```

#### Access

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

#### Notes

- An unknown SKU comes back as `valid: false` with a `type: "item"` failure and a `200`, not a `404`.

#### Errors

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

#### See also

- `POST /storefront/rental`

### 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. |
| `sku` | path | string | yes | Rental item SKU. |

### Request body

The customer details to validate.

```json
{
  "email": "ada@example.com",
  "dateOfBirth": "1990-05-12",
  "country": "US",
  "state": "CA"
}
```

### Responses

| Status | Meaning |
| --- | --- |
| `201` | Whether the customer qualifies, and what failed if not |
| `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. |
| `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
{
  "valid": true,
  "failed": []
}
```

