# Stowbo · Fees

Part of AppEngine API Documentation. Full index: https://appengine.appmint.io/documentation.md
## GET /stowbo/platform-fees

**Get the platform fee list**

`operationId: StowboController_getPlatformFees`

Returns the platform's own fees — the rows the platform adds to every bill. They are `stowbo_fee` records of type `platform`, `tax` or `processing` that are not `inactive`, symmetric with a host's listing fees: any number, of any type.

The list is cached for 30 seconds per node because pricing reads it on a hot path, so a change made through `POST` can take that long to appear on another node.

#### Signature

```http
GET /stowbo/platform-fees () -> The active platform fees, normalised. Empty when none are set.
```

#### Access

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

#### Notes

- A failed read yields `[]` rather than an error — pricing must not fail because fees are unconfigured.

#### Errors

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

#### See also

- `POST /stowbo/platform-fees`

### Parameters

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

### Responses

| Status | Meaning |
| --- | --- |
| `200` | The active platform fees, normalised. Empty when none are set. |
| `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
[
  {
    "name": "service",
    "code": "service",
    "label": "Service fee",
    "type": "platform",
    "paidBy": "guest",
    "trigger": "booking",
    "appliesTo": "checkout",
    "basis": "percent",
    "amount": 8,
    "channels": [],
    "spaceTypes": [],
    "taxable": true,
    "refundable": false,
    "sortOrder": 100,
    "status": "active"
  }
]
```

## POST /stowbo/platform-fees

**Replace the platform fee list**

`operationId: StowboController_setPlatformFees`

**Replaces the platform fee list** with what you send. Each entry is matched to an existing platform fee by `name` (falling back to `code`): a match is updated and set `active`, anything new is created as a `stowbo_fee` record, and every existing platform fee you did not send is set `inactive` — never deleted. An empty array switches them all off.

Entries are normalised on the way in (see the schema defaults) and the stored, normalised list is returned.

#### Signature

```http
POST /stowbo/platform-fees (body) -> The stored, normalised fee list
```

#### Access

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

#### Notes

- A body without `fees` is refused rather than read as "no fees" — it used to switch every platform fee off.
- The read cache is cleared on write for the node that handled it; other nodes can serve the old list for up to 30 seconds.

#### Errors

| Status | Code | Message | When | What to do |
| --- | --- | --- | --- | --- |
| `400` | FEES_REQUIRED | Send { fees: [...] } — the full list of platform fees to keep | The body has no `fees` array. | Send the full list; `[]` switches every fee off. |

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

#### See also

- `GET /stowbo/platform-fees`

### 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 complete fee list.

```json
{
  "fees": [
    {
      "name": "service",
      "title": "Service fee",
      "basis": "percent",
      "amount": 8
    },
    {
      "name": "handling",
      "title": "Handling",
      "appliesTo": "item",
      "amount": 2.5
    }
  ]
}
```

### Responses

| Status | Meaning |
| --- | --- |
| `201` | The stored, normalised fee list |
| `400` | Send { fees: [...] } — the full list of platform fees to keep — The body has no `fees` array. |
| `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. |

