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

# Conventions

> Patterns every Retailgrid API endpoint follows - bulk responses, async jobs, identifiers, decimals, and timestamps.

Read this once and you will be able to use any endpoint in the API without surprises.

## Identifiers

Two kinds of ids show up in the API:

* **`item_id` / `sku` / `competitor_name` / etc.** - your stable business identifier. You provide it. Use it for upserts. `item_id` is the unique SKU-level key for products and transactions; `product_id` is an optional product-level code that groups related items.
* **`id` / `entity_id`** - Retailgrid's internal row id, returned in responses. Use it in path parameters when patching or deleting (`PATCH /v1/products/{entity_id}`).

`entity_id` is a UUID for products, product variants, and transactions. **For competitor prices it is an integer** - the only place this varies in the API.

## Bulk endpoints

Every entity has a `POST /v1/<entity>/bulk` endpoint that accepts an `items` array and returns per-item results.

**Request:**

```json theme={null}
{
  "items": [
    { "item_id": "SKU-001", "product_name": "Item one" },
    { "item_id": "SKU-002", "product_name": "Item two" }
  ]
}
```

**Response (`BulkResponse`):**

```json theme={null}
{
  "results": [
    { "index": 0, "ok": true,  "id": "1c2d..." },
    { "index": 1, "ok": false, "code": "validation_error", "message": "product_name too long" }
  ]
}
```

A bulk request is **never** all-or-nothing. Successful rows are written even if other rows fail. Always inspect each `BulkItemResult.ok` before declaring victory.

Recommended batch size: **500 to 2,000 items per request** depending on row width. Larger payloads will work but increase latency and the chance of a transport-level retry.

## Async jobs

The `POST /v1/imports/*` endpoints accept a multipart CSV and return immediately with a `JobHandle`:

```json theme={null}
{ "job_id": "0b3c1f4a-9c1d-4f2a-8a3b-2c3d4e5f6a7b" }
```

Poll status with `GET /v1/jobs/{job_id}`:

```json theme={null}
{
  "job_id": "0b3c1f4a-9c1d-4f2a-8a3b-2c3d4e5f6a7b",
  "job_type": "import_products",
  "status": "running",
  "progress": 47,
  "created_at": "2026-05-04T10:00:00Z",
  "started_at": "2026-05-04T10:00:01Z"
}
```

`status` is one of `queued`, `running`, `completed`, `failed`. A reasonable polling cadence is every **5-10 seconds** for active jobs and every **30-60 seconds** for queued jobs.

## Sync vs async

| You want to...                    | Use              | Endpoint family                                       |
| --------------------------------- | ---------------- | ----------------------------------------------------- |
| Push a few records right now      | Sync, JSON       | `POST /v1/<entity>/{entity_id}` or `/bulk`            |
| Refresh a full catalog from a CSV | Async, multipart | `POST /v1/imports/<entity>` then poll `/v1/jobs/{id}` |

See [Imports vs bulk](/api-reference/imports-vs-bulk) for the full decision tree.

## Decimals

Money, quantities, and rates are returned as **strings**, not floats. This avoids the rounding surprises you would get with IEEE-754:

```json theme={null}
{ "regular_price": "19.99", "tax_rate": "0.20" }
```

You can submit them as either numbers or strings on the way in, but expect strings in responses. Parse with a decimal library on the client side.

## Timestamps

All timestamps are **ISO 8601 in UTC**:

```
2026-05-04T10:00:00Z
```

Date-only fields use `YYYY-MM-DD`. The API does not perform timezone conversion - send UTC, get UTC.

## Idempotency

`POST /v1/<entity>/{entity_id}` is an **upsert keyed on the path's `entity_id`**, which makes it safe to retry. `PATCH` is also idempotent at the field level. `DELETE` is idempotent (deleting an already-deleted row returns `204`).

The `/bulk` endpoints are idempotent per item, keyed on the business identifier inside each item (`item_id`, `sku`, etc.).

There is no `Idempotency-Key` header today - we will add one before we recommend this API for payment-adjacent flows.

## Pagination

The current write-focused endpoints do not paginate (there are no list endpoints in `v1`). When read endpoints land they will use cursor-based pagination; they will be added under a new section in this reference.

## Rate limits

Rate limits are applied per API key. Today's limits are generous enough that well-behaved sync pipelines do not hit them; if you do, you will receive `429 Too Many Requests` with a `Retry-After` header. Specific numbers will be published here once we have stable production data.
