# Products

> Read your product catalog and semantic search

- Page: https://tbit.app/docs/api/products
- Base URL: `https://rest-api.tbit.app/v1`
- Authentication: `X-API-Key` header with your key on every request (the examples read it from the `TBIT_API_KEY` environment variable)

## Overview

The products endpoints expose your agent's catalog: list it, fetch a single product, or run a semantic search over it. Only active (published) products are returned: drafts are never exposed.

Publishable key: for catalog read endpoints the API key acts like a publishable key (it identifies your tenant and meters usage; catalog data is public). It is safe to use from browser code, just like a Stripe publishable key.

## Endpoints

| Method | Path | Description |
| --- | --- | --- |
| `GET` | `/v1/products` | List active products |
| `GET` | `/v1/products/:id` | Get a single product |
| `GET` | `/v1/products/search` | Semantic search over the catalog |

## List Products

Returns a paginated list of active products. Filter by category tag or kind:

| Parameter | Type | Description |
| --- | --- | --- |
| `limit` | `number` | 1-100, default 50 |
| `offset` | `number` | Pagination offset, default 0 |
| `tag` | `string` | Filter by category tag |
| `kind` | `string` | 'product' or 'service' |

`GET /v1/products`

```bash
curl "https://rest-api.tbit.app/v1/products?limit=20&tag=fiestas&kind=product" \
  -H "X-API-Key: $TBIT_API_KEY"
```

Response:

```json
{
  "data": [
    {
      "_id": "prod_id_1",
      "kind": "product",
      "title": "Kit fiesta dinosaurios",
      "description": "Decoración completa para fiesta infantil",
      "media": [
        { "url": "https://api.tbit.app/media/dino-kit.jpg", "alt": "Kit fiesta", "type": "image", "position": 0 }
      ],
      "category_tags": ["fiestas", "infantil"],
      "currency": "COP",
      "base_price": 95000,
      "compare_at_price": 120000,
      "variants": [
        {
          "id": "var_id_1",
          "title": "Grande",
          "price": 95000,
          "selected_options": [{ "name": "Tamaño", "value": "Grande" }]
        }
      ],
      "created_at": "2024-09-01T12:00:00Z",
      "updated_at": "2024-09-01T12:00:00Z"
    }
  ],
  "pagination": {
    "total": 134,
    "limit": 20,
    "offset": 0,
    "has_more": true
  }
}
```

## Get a Product

Fetch a single product by id. Drafts, unknown ids, and products belonging to another agent all return a 404:

`GET /v1/products/prod_id_1`

```bash
curl "https://rest-api.tbit.app/v1/products/prod_id_1" \
  -H "X-API-Key: $TBIT_API_KEY"
```

Response:

```json
{
  "error": {
    "code": "NOT_FOUND"
  }
}
```

## Semantic Search

Search uses vector embeddings, not keyword matching: a query like "fiesta de dinosaurios" finds dinosaur-themed party products even if no product title contains those exact words. Search is rate limited to 30 requests per minute per key (on top of the global 100/min limit). Each result may include a relevance score:

`GET /v1/products/search`

```bash
curl "https://rest-api.tbit.app/v1/products/search?q=fiesta%20de%20dinosaurios&limit=10" \
  -H "X-API-Key: $TBIT_API_KEY"
```

Response:

```json
{
  "data": [
    {
      "_id": "prod_id_1",
      "kind": "product",
      "title": "Kit fiesta dinosaurios",
      "currency": "COP",
      "base_price": 95000,
      "score": 0.91
    }
  ]
}
```

## The Product Object

All endpoints return product objects with the following shape. base_price is a plain number in the catalog currency (e.g. COP, no minor units). compare_at_price is only present when the product is on sale and is always higher than base_price:

| Field | Type | Description |
| --- | --- | --- |
| `_id` | `string` | Product id |
| `kind` | `string` | 'product' or 'service' |
| `title` | `string` | Product title |
| `description` | `string` | Product description |
| `media` | `array` | Array of { url, alt?, type: 'image' \| 'video', position? } |
| `category_tags` | `string[]` | Category tags |
| `currency` | `string` | ISO 4217 currency code |
| `base_price` | `number` | Price as a plain number (e.g. COP, no minor units) |
| `compare_at_price` | `number?` | Only when on sale; always higher than base_price |
| `variants` | `array` | Array of { id, title, price, compare_at_price?, selected_options: [{ name, value }] } |
| `created_at` | `string` | ISO 8601 timestamp |
| `updated_at` | `string` | ISO 8601 timestamp |
