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

# Custom API Integration

> Connect Cartally to any store by providing a product API endpoint.

If your store runs on a platform not natively supported by Cartally, you can integrate using the **Custom API** method. You build an API endpoint on your server that returns your product catalog — Cartally fetches it periodically and indexes it for search.

## Overview

```mermaid theme={null}
sequenceDiagram
    participant Cartally
    participant Your API
    Cartally->>Your API: GET /products?page=1&per_page=100
    Your API-->>Cartally: JSON array of products
    Cartally->>Your API: GET /products?page=2&per_page=100
    Your API-->>Cartally: JSON array of products
    Note over Cartally: Index products for search
```

## Setup

1. Log in to [app.cartally.co](https://app.cartally.co).
2. Go to **Integration** and select **API** as the method.
3. Enter the full URL of your product endpoint (e.g. `https://myshop.com/api/cartally/products`).
4. (Optional) Enter an API key if your endpoint requires authentication.
5. Click **Save & Connect**.

Cartally will call your endpoint with a `GET` request. If authentication is configured, the request includes an `Authorization: Basic {api_key}` header.

## Product endpoint specification

Your endpoint must return a JSON array of product objects. Cartally fetches products in pages using query parameters.

### Request

```
GET https://myshop.com/api/cartally/products?page=1&per_page=100&lang=en
```

| Parameter  | Type    | Description                                                           |
| ---------- | ------- | --------------------------------------------------------------------- |
| `page`     | integer | Page number (starts at 1)                                             |
| `per_page` | integer | Number of products per page (default: 100)                            |
| `lang`     | string  | Language code (e.g. `en`, `pl`). Return product data in this language |

### Response

Return a JSON array of product objects. When the last page is reached, return an empty array `[]`.

```json theme={null}
[
  {
    "id": 12345,
    "name": "Mountain Bike Helmet Pro",
    "description": "Lightweight polycarbonate helmet with adjustable ventilation and MIPS protection system. Certified for mountain biking and trail riding.",
    "reference": "HEL-PRO-001",
    "url": "https://myshop.com/product/mountain-bike-helmet-pro",
    "price": 49.99,
    "categories": ["Helmets", "Safety Equipment"],
    "images": [
      "https://myshop.com/img/helmet-black-1.jpg",
      "https://myshop.com/img/helmet-black-2.jpg"
    ],
    "variants": [
      {
        "id": "12345-black",
        "attributes": {
          "color": "black",
          "size": "M"
        },
        "price": 49.99,
        "images": [
          "https://myshop.com/img/helmet-black-1.jpg"
        ]
      },
      {
        "id": "12345-white",
        "attributes": {
          "color": "white",
          "size": "M"
        },
        "price": 49.99,
        "images": [
          "https://myshop.com/img/helmet-white-1.jpg"
        ]
      }
    ],
    "currencies": {
      "EUR": 49.99,
      "PLN": 219.00,
      "USD": 54.99
    }
  }
]
```

## Product fields reference

### Required fields

| Field         | Type              | Description                                                                                                                 |
| ------------- | ----------------- | --------------------------------------------------------------------------------------------------------------------------- |
| `id`          | string or integer | Unique product identifier                                                                                                   |
| `name`        | string            | Product title. Should be descriptive — this is the primary search field                                                     |
| `description` | string            | Product description in plain text or HTML (HTML tags will be stripped). The more detailed, the better the AI search quality |
| `categories`  | string\[]         | List of category names the product belongs to. Used for faceted filtering                                                   |
| `images`      | string\[]         | List of publicly accessible image URLs. The first image is used as the main thumbnail                                       |
| `price`       | number            | Product price in the store's default currency (decimal, e.g. `49.99`)                                                       |

### Recommended fields

| Field        | Type      | Description                                                                                  |
| ------------ | --------- | -------------------------------------------------------------------------------------------- |
| `url`        | string    | Direct URL to the product page in your store. Customers click this to visit the product      |
| `reference`  | string    | SKU or internal reference code                                                               |
| `variants`   | object\[] | Product variants (see below). If omitted, the product is indexed as a single item            |
| `currencies` | object    | Prices in additional currencies. Keys are ISO 4217 currency codes, values are decimal prices |

### Variant fields

If your products have color variants, provide them in the `variants` array. Cartally splits color variants into separate search results so each variant shows the correct image.

| Field        | Type      | Description                                                                    |
| ------------ | --------- | ------------------------------------------------------------------------------ |
| `id`         | string    | Unique variant identifier (e.g. `12345-black`)                                 |
| `attributes` | object    | Key-value pairs of variant attributes (e.g. `{"color": "black", "size": "M"}`) |
| `price`      | number    | Variant-specific price (if different from the parent product)                  |
| `images`     | string\[] | Variant-specific images. If omitted, parent product images are used            |

<Note>
  The `color` attribute has special handling — variants with different colors become separate search results with their own images. Other attributes (like size) are indexed as filterable facets but don't split the product.
</Note>

### Multi-currency pricing

To support multiple currencies, include a `currencies` object with ISO 4217 codes as keys:

```json theme={null}
{
  "currencies": {
    "PLN": 219.00,
    "EUR": 49.99,
    "USD": 54.99,
    "GBP": 42.99
  }
}
```

If `currencies` is omitted, only the base `price` field is indexed using your store's default currency.

## Multilingual products

If your store supports multiple languages, Cartally will call your endpoint with different `lang` parameter values. Your endpoint should return product names, descriptions, and categories in the requested language.

```
GET /api/cartally/products?page=1&per_page=100&lang=pl
```

If a product is not translated, return it in the default language — Cartally handles the fallback gracefully.

### Declaring available languages

Cartally detects available languages from the `<html lang>` attribute and `<link rel="alternate" hreflang="...">` tags on your store's homepage. Make sure these are present:

```html theme={null}
<html lang="pl">
<head>
  <link rel="alternate" hreflang="pl" href="https://myshop.com" />
  <link rel="alternate" hreflang="en" href="https://myshop.com/en/" />
  <link rel="alternate" hreflang="de" href="https://myshop.com/de/" />
</head>
```

## Pagination

Your endpoint must support pagination. Cartally fetches products in batches of 100 (configurable).

**Rules:**

* Return up to `per_page` products per request.
* When there are no more products, return an empty array `[]`.
* Products should be returned in a consistent order (e.g. by ID) so pagination is stable.

**Example pagination flow:**

| Request                | Response                     |
| ---------------------- | ---------------------------- |
| `?page=1&per_page=100` | 100 products                 |
| `?page=2&per_page=100` | 100 products                 |
| `?page=3&per_page=100` | 47 products                  |
| `?page=4&per_page=100` | `[]` (empty — sync complete) |

## Authentication

If your endpoint requires authentication, enter an API key in the Cartally panel. Cartally sends it as:

```
Authorization: Basic {api_key}
```

<Warning>
  Always use HTTPS for your product endpoint. API keys sent over HTTP are vulnerable to interception.
</Warning>

## Sync schedule

* The first sync runs immediately after saving the integration.
* Subsequent syncs run automatically every 24 hours (configurable).
* You can trigger a manual re-sync from the **Integration** page in the Cartally panel.

## Minimal example

Here's the simplest possible product endpoint (Python/Flask):

```python theme={null}
from flask import Flask, request, jsonify

app = Flask(__name__)

PRODUCTS = [
    {
        "id": 1,
        "name": "Classic T-Shirt",
        "description": "100% cotton t-shirt, available in multiple colors.",
        "categories": ["Clothing", "T-Shirts"],
        "images": ["https://myshop.com/img/tshirt.jpg"],
        "price": 29.99,
        "url": "https://myshop.com/product/classic-tshirt",
        "variants": [
            {
                "id": "1-black",
                "attributes": {"color": "black", "size": "M"},
                "images": ["https://myshop.com/img/tshirt-black.jpg"]
            },
            {
                "id": "1-white",
                "attributes": {"color": "white", "size": "M"},
                "images": ["https://myshop.com/img/tshirt-white.jpg"]
            }
        ]
    },
    {
        "id": 2,
        "name": "Running Shoes",
        "description": "Lightweight running shoes with responsive cushioning.",
        "categories": ["Footwear", "Running"],
        "images": ["https://myshop.com/img/shoes.jpg"],
        "price": 89.99,
        "url": "https://myshop.com/product/running-shoes"
    }
]

@app.route("/api/cartally/products")
def products():
    page = int(request.args.get("page", 1))
    per_page = int(request.args.get("per_page", 100))
    # lang = request.args.get("lang", "en")  # Use for translations

    start = (page - 1) * per_page
    end = start + per_page
    return jsonify(PRODUCTS[start:end])
```

## Best practices

<AccordionGroup>
  <Accordion title="Write detailed product descriptions">
    The AI assistant uses product descriptions to answer customer questions. The more detailed your descriptions, the better the AI can recommend and compare products.
  </Accordion>

  <Accordion title="Use consistent category names">
    Categories become filter facets in the widget. Use consistent naming (e.g. always "T-Shirts", not sometimes "Tshirts" or "tee shirts").
  </Accordion>

  <Accordion title="Provide high-quality images">
    The first image in the array is used as the search result thumbnail. Use square or landscape images for best results.
  </Accordion>

  <Accordion title="Include variant images">
    If a product comes in multiple colors, provide color-specific images in each variant. This way customers see the correct color in search results.
  </Accordion>

  <Accordion title="Keep your endpoint fast">
    Cartally fetches up to 100 products per request. Your endpoint should respond within 30 seconds. Use database pagination — don't load all products into memory.
  </Accordion>
</AccordionGroup>

## Troubleshooting

### Connection test fails

* Verify the endpoint URL is correct and publicly accessible.
* Check that HTTPS is working (Cartally requires a valid SSL certificate).
* If using an API key, verify it's entered correctly in the panel.

### Products not appearing in search

* Check that your endpoint returns valid JSON.
* Verify that `name`, `description`, `categories`, `images`, and `price` are present for every product.
* Check that pagination returns an empty array `[]` on the last page (not an error).
* Wait a few minutes after the sync — indexing may still be in progress.

### Wrong prices or languages

* Ensure your endpoint responds to the `lang` parameter with correctly translated data.
* Verify that `currencies` values are decimal numbers (not strings, not minor units).
