# Storefront APIs

> Build your own customer app or website on top of a Fina business: app key, envelope, /config, delivery quotes and weight-based shipping.

The official white-label apps of Fina businesses are built on these APIs, and you can use them too. Each API is scoped to **one business**: the app key identifies it, and no endpoint lists or discovers other businesses.

| Vertical | Base path | App key header |
| --- | --- | --- |
| [Farm storefront API](https://fina.do/developers/api-farm.md) | `/api/farm/v1` | `X-Farm-App-Key` |
| [Boutique storefront API](https://fina.do/developers/api-boutique.md) | `/api/boutique/v1` | `X-Boutique-App-Key` |
| [Restaurant storefront API](https://fina.do/developers/api-restaurant.md) | `/api/restaurant/v1` | `X-Restaurant-App-Key` |
| [Barbershop booking API](https://fina.do/developers/api-barbershop.md) | `/api/barbershop/v1` | `X-Barbershop-App-Key` |
| [Real estate API](https://fina.do/developers/api-real-estate.md) | `/api/real-estate/v1` | `X-Real-Estate-App-Key` |
| [Rent a car API](https://fina.do/developers/api-rent-car.md) | `/api/rent-car/v1` | `X-Rent-Car-App-Key` |

### Authentication

- **App key** — created by the business owner in their panel, in the module's customer-app section. It is an identifier and rate-limit scope, not a secret: it ships inside the app.
- **Customer token** — private routes (`/me`, `/orders`, `/addresses`…) also need `Authorization: Bearer <access token>` of the customer's Fina account (Supabase Auth JWT). Public routes (`/config`, `/catalog`, `/windows`, slots…) work with the app key alone.

### Response envelope

Farm, boutique, restaurant and barbershop answer `{ "data": …, "error": null, "request_id": "…" }`; on failure `data` is `null` and `error` is `{ "code", "message", "details"? }` with a stable upper-case `code` (`RATE_LIMITED`, `OUT_OF_DELIVERY_ZONE`, `SHIPMENT_LIMIT_EXCEEDED`…). Every response has an `X-Request-Id` header — include it when contacting support.

**First call — curl**

```bash
curl https://fina.do/api/boutique/v1/config \
  -H "X-Boutique-App-Key: $APP_KEY"
```

**First call — TypeScript**

```ts
const res = await fetch('https://fina.do/api/boutique/v1/config', {
  headers: { 'X-Boutique-App-Key': process.env.APP_KEY! },
})
const { data, error, request_id } = await res.json()
if (error) throw new Error(`${error.code} (request ${request_id})`)
console.log(data.business.name)
```

### Start with /config

`GET /config` returns the business identity and everything the app needs to boot: fulfilment modes, delivery pricing (`delivery_fee_by_order_total`, `delivery_fee_by_weight` with its `weight_unit`), payment options (`card_enabled`, `cash_on_delivery`) and whether Fina Pay subscriptions are available. Cache it and refresh on app start.

### Delivery quotes and weight-based shipping

`POST /delivery/estimate` with an address returns `fee_minor`, `distance_meters`, the delivery zone and `pricing_mode` (`flat`, `per_km`, `tiers`, `order_total`, `by_weight`). For weight-based shipping send the cart as `items: [{ product_id, variant_id?, quantity }]`: the response adds `weight` (real, volumetric and billable grams), `fee_by_weight` and the zone limits. If the cart exceeds every zone's maximum weight or size you get `deliverable: false` with `reason: "too_heavy" | "too_large"`, and creating the order answers `409 SHIPMENT_LIMIT_EXCEEDED`.

### Rate limits

Per-minute limits per app key and client. Over the limit: `429` with `error.code = "RATE_LIMITED"` and `details.retry_after_seconds`.

Source: https://fina.do/developers#storefront-apis