# Fittle – integration guide for portals and custom systems

Fittle is a 3D furniture configurator (kitchen, built-in wardrobe, living-room wall,
furniture) that you embed on your website. The user designs the furniture and sends a
request; you receive it by e-mail, by **webhook** and through the **API**, so you can
distribute it to your craftsmen. *(Slovenská verzia: [PORTAL.sk.md](PORTAL.sk.md).)*

## 1. Embedding the configurator

```html
<div id="configurator"></div>
<script src="https://app.getfittle.com/embed.js"
        data-key="FITT-XXXX-XXXX-XXXX" data-product="kitchen"
        data-target="#configurator"></script>
```

`data-product`: `kitchen` | `wardrobe` | `living` | `furniture`. The key `FITT-…` is bound to
your domains (set them in your account). Programmatic use:

```html
<script src="https://app.getfittle.com/embed.js" data-key="FITT-XXXX-XXXX-XXXX"></script>
<script>
  HNL.mount('#configurator', {
    product: 'wardrobe',
    fill: true,                                  // 100 % of the parent's height (default 820 px)
    prefill: { name: 'Jane Doe', email: 'jane@example.com', phone: '+421900000000', note: '' }
  });
  HNL.on('ready', function (e) { console.log('running', e.product); });
  HNL.on('quote', function (e) { console.log('request sent', e.id, e.design); });
  HNL.getDesign(function (design) { /* current design as JSON */ });
  HNL.prefill({ name: '…', email: '…' });         // any time later
</script>
```

Events (`HNL.on`): `ready` {product}, `quote` {id, product, design}, `design` (reply to
`getDesign`), `height` {height}. Prefill values go into the request form; the user can edit them.

## 2. Webhook (recommended)

In your account (`/stolar/` → Integration) set a **Webhook URL** (https) and a **secret**.
On every request we send:

```
POST <webhookUrl>
Content-Type: application/json
X-Fittle-Event: quote.created            (or webhook.test)
X-Fittle-Delivery: WMTZ…                 (delivery id – idempotency key)
X-Fittle-Timestamp: 1789300000           (unix seconds)
X-Fittle-Signature: sha256=<hex>         HMAC-SHA256(secret, timestamp + "." + rawBody)

{ "id": "WMTZ…", "event": "quote.created", "createdAt": "…", "shopKey": "FITT-…",
  "data": { "design": { "id": "DMTZ…", "product": "kitchen", "status": "new",
            "customer": { "name": "…", "email": "…", "phone": "…", "note": "…", "photoCount": 2 },
            "design": { … design JSON … }, "createdAt": "…" } } }
```

Verify the signature (constant-time compare) and answer **2xx within 8 s**. On failure we retry
after 10 s, 1 min and 5 min, then give up (fetch the request through the API). The same
`X-Fittle-Delivery` means the same delivery. Customer photos are not included in the webhook –
download them through the API.

Signature check (Node.js):

```js
const crypto = require('crypto');
function verify(req, rawBody, secret) {
  const want = 'sha256=' + crypto.createHmac('sha256', secret)
    .update(req.headers['x-fittle-timestamp'] + '.' + rawBody).digest('hex');
  const got = req.headers['x-fittle-signature'] || '';
  return got.length === want.length && crypto.timingSafeEqual(Buffer.from(got), Buffer.from(want));
}
```

Test event: the "Send test webhook" button in your account, or `POST /api/portal/webhook/test`.

## 3. API

Issue an **API key** in your account (shown once, `fak_…`). All calls use
`Authorization: Bearer fak_…`, JSON bodies, limit 600 requests / 10 min. Error responses are
`{ "error": "…", "reason": "…", "reqId": "…" }` – quote `reqId` when reporting a problem.

| Method | Path | Description |
|---|---|---|
| GET | `/api/portal/me` | your profile (key, plan, webhook) |
| GET | `/api/portal/designs?limit=50&since=<ISO>&status=new` | requests, newest first (photo metadata only) |
| GET | `/api/portal/designs/:id?photos=1` | one request; with `photos=1` the photo list includes download URLs |
| GET | `/api/portal/designs/:id/photos/:n` | one customer photo (binary, private – API key required) |
| POST | `/api/portal/designs/:id/status` `{ "status": "seen" }` | `new` → `seen` → `contacted` → `quoted` → `won` / `lost` |
| POST | `/api/portal/designs/:id/view-link` `{ "days": 30 }` | **3D preview link** for your craftsman – no account, signed, valid 30 days (`…/c/<product>?k=…&load=<id>&vt=…&view=1`) |
| POST | `/api/portal/webhook/test` | sends a `webhook.test` delivery |

## 4. Design JSON and loading it back

`design` is what the configurator exports: a human-readable description (product type,
dimensions, I/L/U layout, modules/cabinets with contents, decors, handles, appliances, room) –
**localised to the language the customer used** – plus the field **`state`**: the raw,
language-independent configurator state (codes, not labels). With `state` the design can be
**loaded back into 3D** at any time:

- via the link from `view-link` (the craftsman opens a 3D preview in the browser, no account),
- in the configurator with the **Load design** button (a JSON file from **Save design** or from the API),
- programmatically inside the iframe: `HNL_CORE.loadDesign(json)` (accepts the whole request record or just `design`).

The format is stable across versions (new fields are only added). The craftsman receives the
same JSON in the e-mail.
