> ## 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**:

```text theme={null}
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

List endpoints (products, transactions, grids, jobs, approved prices) use **cursor-based pagination**. Pass `limit` to set the page size, and to fetch the next page pass the `cursor` value returned as `next_cursor` in the previous response. A `null` `next_cursor` means there are no more rows.

## Usage limits

Usage is metered **per organization** — the budget is shared across every API key, every user, and the MCP connector, so adding more keys or spreading calls across endpoints does not raise your ceiling. When the organization's usage budget is exhausted, the API rejects the request **before** it does any expensive processing and returns `429 Too Many Requests`, a `Retry-After` header, and a JSON body reporting the limit, the remaining allowance, and when it resets (see [Errors](/api-reference/errors#organization-usage-limit-429)).

A separate per-key rate limit also applies and likewise returns `429` with a `Retry-After` header. Today's limits are generous enough that well-behaved sync pipelines do not hit them; specific numbers will be published here once we have stable production data. The one rule that matters for pipeline design: back off on `429` and honour `Retry-After` rather than retrying immediately.


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