> ## Documentation Index
> Fetch the complete documentation index at: https://docs.kleep.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Conversational agents

> Bring Kleep size recommendations into a conversational agent (customer-service agent, AI shopping assistant or AI agent): one call per answer, ready-to-say results, profiles that follow the shopper.

This guide is for conversational agents (customer-service agents, AI shopping assistants and AI agents on platforms such as Gorgias or DigitalGenius) that answer sizing questions for Kleep's retailers. The **Kleep Partner API** is built for conversations:

* **One call per answer.** Send the product and what the shopper told you; get the size back.
* **The API tells you what to ask.** Missing answers come back as localized questions, so your bot never guesses which information Kleep needs.
* **Ready-to-say results.** Every recommendation includes a one- or two-sentence `summary` in the shopper's language, plus structured fields if you want to phrase it yourself.
* **Profiles follow the shopper.** A shopper who already used Kleep on the retailer's website gets their size without any question, and a profile created in chat is reused on the website.

> [!NOTE]
>
> The Partner API is in early access. Contact [support@kleep.ai](mailto:support@kleep.ai) to get a sandbox API key and a test retailer.

## What shoppers can ask

| Shopper asks | You call | You get |
| - | - | - |
| "Which size should I take?" | [`POST /recommendations`](/api-reference/create-recommendation) | The size for this shopper, its fit per body zone, the neighbouring size and a ready-to-say `summary` |
| "Does this run small?" | [`GET /products/{product_id}/sizing`](/api-reference/get-product-sizing) | How the product fits compared to its size chart, as a tendency and a sentence |
| "Is the M in stock?" | [`GET /products/{product_id}/sizing`](/api-reference/get-product-sizing) | The product's sizes with availability |
| "The M was too big" | [`POST /recommendations/{id}/feedback`](/api-reference/send-feedback) | Nothing to say back: Kleep learns from it |

Every endpoint, with request and response schemas and a live playground, is in the **Partner API reference** in the sidebar.

## Concepts

| Concept | What it is |
| - | - |
| **Partner API key** | Identifies your platform. One key for all the retailers you serve: `kp_test_…` on the sandbox, `kp_live_…` in production. Server-side only |
| **Retailer** | A Kleep customer whose shoppers you talk to, identified by its Kleep `public_id`. Sent in the `Kleep-Retailer` header on every call. The retailer must enable your platform first (see [Enable a retailer](#enable-a-retailer)) |
| **Product** | The retailer's own product ID, as in the catalog it shares with Kleep. Variants use the retailer's variant IDs, so you can add the recommended size to the cart directly |
| **Profile** | A shopper's fit profile (`prf_…`): their measurements and body shape. Created by the first recommendation, reused for any product of that retailer |
| **Recommendation** | One size for one profile and one product (`rec_…`). Used for feedback and attribution |

## Authentication

Send your API key as a bearer token and the retailer in `Kleep-Retailer`:

```bash theme={null}
curl https://fit.kleep.ai/v1/products/JEAN-0142/sizing \
  -H "Authorization: Bearer kp_live_..." \
  -H "Kleep-Retailer: 9b1f3c2e-5d4a-4f6b-8e7c-1a2b3c4d5e6f"
```

Call the API from your backend. Never put the API key in a browser, a mobile app, or a prompt.

### Enable a retailer

A retailer turns on your platform once, from the Kleep dashboard (**Settings → Integrations**) or by asking its Kleep representative. Until then, calls for that retailer return `403 retailer_not_enabled`. Store the retailer's `public_id` in your per-retailer settings: the retailer finds it in the same dashboard screen.

## Quickstart: one size in one call

**1. Send the product and what you know**

```bash theme={null}
curl -X POST https://fit.kleep.ai/v1/recommendations \
  -H "Authorization: Bearer kp_live_..." \
  -H "Kleep-Retailer: 9b1f3c2e-5d4a-4f6b-8e7c-1a2b3c4d5e6f" \
  -H "Content-Type: application/json" \
  -d '{
    "product_id": "JEAN-0142",
    "shopper": { "measurements": { "gender": "female" } },
    "locale": "en-GB",
    "market": "GB",
    "conversation_id": "conv_5521"
  }'
```

**2. Ask what's missing**

Kleep answers `422` with the questions to ask, already localized:

```json theme={null}
{
  "error": {
    "code": "missing_inputs",
    "message": "Height, weight and age are needed",
    "missing": ["height_cm", "weight_kg", "age"],
    "questions": [
      { "id": "height_cm", "required": true, "type": "number", "unit": "cm", "min": 120, "max": 230, "prompt": "How tall are you?" },
      { "id": "weight_kg", "required": true, "type": "number", "unit": "kg", "min": 20, "max": 250, "prompt": "How much do you weigh?" },
      { "id": "age", "required": true, "type": "integer", "min": 12, "max": 100, "prompt": "How old are you?" }
    ]
  }
}
```

Ask them in one message, in your bot's own voice: "To find your size, could you tell me your height, weight and age?"

**3. Send the answers, get the size**

Call again with the answers in `measurements`:

```json theme={null}
{
  "recommendation_id": "rec_7Hq2mV9xKp",
  "profile_id": "prf_3Nf8sLw1Qz",
  "product_id": "JEAN-0142",
  "size": { "label": "M", "variant_id": "JEAN-0142-M", "available": true },
  "alternatives": [
    { "label": "L", "variant_id": "JEAN-0142-L", "direction": "larger", "available": true, "reason": "For a looser fit" }
  ],
  "fit": [
    { "zone": "waist", "fit": "just_right" },
    { "zone": "hips", "fit": "slightly_tight" }
  ],
  "product_fit": { "tendency": "runs_small", "score": -0.4 },
  "summary": "I'd recommend size M. It should feel slightly snug at the hips; take an L if you prefer a looser fit."
}
```

Say the `summary`, or build your own sentence from `size`, `fit` and `alternatives`.

**4. Keep the profile**

Store `profile_id` with the conversation, or with the shopper's record if you know who they are. Next time, send `"shopper": { "profile_id": "prf_3Nf8sLw1Qz" }` and skip the questions, for any product of that retailer.

## Design the conversation

### When to offer size help

Call `GET /products/{product_id}/sizing` as soon as the conversation is about a product. Offer size help only when `supported` is `true`. When it is `false`, answer from the product page's size chart or hand over to a human.

### What Kleep asks

You don't need to hard-code questions: `missing_inputs` tells you what is missing. To script them yourself, `GET /questions?category=clothing` returns the full list in order. For clothing:

| Answer | Required | Values |
| - | - | - |
| `gender` | yes | `female` or `male`: women's or men's sizes. Ask "Do you shop women's or men's sizes?", not about identity |
| `height_cm` | yes | 120–230. Convert feet and inches yourself |
| `weight_kg` | yes | 20–250. Convert pounds and stones yourself |
| `age` | yes | 12–100 |
| `body_shape.stomach` | no | `flat`, `medium`, `round` |
| `body_shape.hips` | no, women's sizes | `narrow`, `medium`, `wide` |
| `body_shape.torso` | no, men's sizes | `slim`, `medium`, `muscular` |
| `body_shape.thighs` | no, men's sizes | `thin`, `medium`, `large` |

Body-shape answers improve accuracy. Ask them only if the shopper is happy to answer more: a skipped answer counts as `medium`. Lingerie also needs `bra.band` and `bra.cup`; `missing_inputs` asks for them when the product requires it.

### Shoppers Kleep already knows

At the start of a conversation with an identified shopper, look them up with `GET /profiles?customer_id=…` (the retailer's customer ID) or `GET /profiles?email=…`. A `200` means the shopper already has a Kleep profile, for example from the retailer's website: send its `profile_id` and answer without a single question. A `404 profile_not_found` means you ask the questions as usual.

When you know the shopper's `customer_id`, send it in `shopper.customer_id` on recommendations too. The profile created in chat then follows the shopper on the retailer's website.

### Corrections and forgetting

* "Actually I'm 170 cm": `PATCH /profiles/{profile_id}` with the new value, then ask for the recommendation again.
* "Forget my measurements": `DELETE /profiles/{profile_id}` erases them.

### Phrase the answer

* Lead with the size: "I'd recommend an **M**."
* Add one fit detail in plain words, from `fit`: "It should feel slightly snug at the hips."
* Offer the alternative only when the shopper hesitates between sizes or mentions how they like the fit.
* If `size.available` is `false`, say so and offer the alternative if it's available, or a back-in-stock alert if the retailer has one.
* Never invent or adjust a size the API didn't return. If the call fails, say you can't check the size right now and point to the size chart.

## Answer "does it run small?"

`GET /products/{product_id}/sizing` returns `product_fit` without any shopper data:

```json theme={null}
{
  "product_id": "JEAN-0142",
  "supported": true,
  "category": "clothing",
  "sizes": [
    { "label": "S", "variant_id": "JEAN-0142-S", "available": true },
    { "label": "M", "variant_id": "JEAN-0142-M", "available": true },
    { "label": "L", "variant_id": "JEAN-0142-L", "available": false }
  ],
  "product_fit": {
    "tendency": "runs_small",
    "score": -0.4,
    "summary": "This style runs slightly small: most shoppers take one size up."
  }
}
```

`tendency` is one of `runs_very_small`, `runs_small`, `true_to_size`, `runs_large`, `runs_very_large`. `product_fit` is `null` when Kleep doesn't have enough data on the product: don't make a claim then. If the shopper wants a size for themselves, move to `POST /recommendations`.

## Use it from an AI agent

Expose two tools to your model. Your backend implements each one with a single API call, adds the API key and `Kleep-Retailer`, and stores `profile_id`.

```json Tool definitions theme={null}
[
  {
    "name": "kleep_recommend_size",
    "description": "Recommend a size for the shopper and a product of this store, using Kleep. Call it whenever the shopper asks which size to take, even if you don't know their measurements yet: the result tells you which questions to ask. Never guess a size yourself.",
    "input_schema": {
      "type": "object",
      "properties": {
        "product_id": { "type": "string", "description": "The store's product ID of the item discussed." },
        "gender": { "type": "string", "enum": ["female", "male"], "description": "Women's or men's sizes, if known." },
        "height_cm": { "type": "number" },
        "weight_kg": { "type": "number" },
        "age": { "type": "integer" },
        "body_shape": { "type": "object", "description": "Optional: stomach, hips (women), torso and thighs (men)." },
        "fit_preference": { "type": "string", "enum": ["fitted", "regular", "loose"] }
      },
      "required": ["product_id"]
    }
  },
  {
    "name": "kleep_product_sizing",
    "description": "Get how a product of this store fits (does it run small or large) and which sizes are in stock. Needs no information about the shopper.",
    "input_schema": {
      "type": "object",
      "properties": { "product_id": { "type": "string" } },
      "required": ["product_id"]
    }
  }
]
```

```ts Reference implementation (Node 18+) theme={null}
const KLEEP = 'https://fit.kleep.ai/v1';

async function kleep(path: string, retailer: string, body?: unknown) {
  const res = await fetch(KLEEP + path, {
    method: body ? 'POST' : 'GET',
    headers: {
      Authorization: `Bearer ${process.env.KLEEP_API_KEY}`,
      'Kleep-Retailer': retailer,
      'Content-Type': 'application/json',
    },
    body: body ? JSON.stringify(body) : undefined,
    signal: AbortSignal.timeout(8000),
  });
  return { status: res.status, data: await res.json() };
}

// Tool: kleep_recommend_size. `session` is your per-conversation storage.
export async function kleepRecommendSize(retailer, session, input) {
  const { product_id, fit_preference, ...measurements } = input;
  const shopper = session.kleepProfileId
    ? { profile_id: session.kleepProfileId, measurements }
    : { measurements, customer_id: session.customerId };
  const { status, data } = await kleep('/recommendations', retailer, {
    product_id, shopper, fit_preference, locale: session.locale, conversation_id: session.id,
  });

  if (status === 200) {
    session.kleepProfileId = data.profile_id;
    return data; // the model reads summary, size, alternatives
  }
  if (data.error?.code === 'missing_inputs') {
    return { ask_the_shopper: data.error.questions.map(q => q.prompt) };
  }
  if (data.error?.code === 'product_not_supported') {
    return { not_supported: 'Kleep cannot size this product. Use the size chart.' };
  }
  return { error: 'Size recommendation unavailable right now.' };
}
```

The model calls `kleep_recommend_size` right away, gets the questions back, asks them, and calls again with the answers. Your code never decides what to ask.

## Measure the impact

Report what happens after a recommendation with `POST /events`. The retailer then sees the conversations' effect on sales and returns in the Kleep dashboard, next to its website:

| Event | Send it when |
| - | - |
| `recommendation_shown` | The size was shown to the shopper |
| `added_to_cart` | The shopper added a size to the cart from the conversation, with `variant_id` |
| `order_placed` | An order followed the conversation, with `order_id` |

Always include the `recommendation_id`. Sending `conversation_id` on recommendations lets Kleep match later orders even when you can't send `order_placed` yourself.

## Errors and limits

Errors share one format. Branch on `error.code`, never on `message`:

```json theme={null}
{ "error": { "code": "product_not_found", "message": "No product JEAN-9999 for this retailer" } }
```

| Status | `code` | What to do |
| - | - | - |
| `400` | `invalid_input` | Fix the field named in `details`. An out-of-range answer usually means a unit mistake |
| `401` | `unauthorized` | Check the API key |
| `403` | `retailer_not_enabled` | The retailer hasn't enabled your platform yet |
| `404` | `product_not_found`, `profile_not_found`, `recommendation_not_found` | The ID doesn't exist for this retailer. For a profile, ask the questions again |
| `422` | `missing_inputs` | Ask the `questions`, then call again |
| `422` | `product_not_supported` | Kleep can't size this product. Use the size chart |
| `429` | `rate_limited` | Wait `Retry-After` seconds |
| `5xx` | `internal_error` | Retry up to 3 times with backoff |

Use an 8-second timeout per call. Each API key gets a rate limit per retailer that comfortably covers live conversations; contact Kleep before running batch jobs. `POST /profiles` accepts an `Idempotency-Key` header so retries never create duplicates.

## Privacy

Height, weight and age are personal data. Kleep processes them for the retailer, as its data processor, only to compute sizes for that retailer, and stores them in the EU.

* Say why you ask before asking: "to find your size in this store".
* Let the shopper decline: answer with `product_fit` and the size chart instead.
* Keep the `profile_id`, not the raw answers. You don't need to store measurements on your side.
* Honour "forget me" with `DELETE /profiles/{profile_id}`.

## Sandbox

Your sandbox key (`kp_test_…`) works with a test retailer that Kleep shares with you, with a sample catalog of products and a few known shoppers for `GET /profiles`. Responses have the same shape as production. Use it in your automated tests: sandbox calls never affect retailer data or dashboards.

## Support

For an API key, enabling a retailer, or questions while integrating, contact [support@kleep.ai](mailto:support@kleep.ai).


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.