> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://docs.coi.co.il/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs.coi.co.il/_mcp/server.

# Getting started

## Base URL

Every call goes to **your store's own address**, over HTTPS:

```
https://www.your-store.co.il/api/v1/...
```

Your store's domain, or its `yoursite.coi.co.il` address - both reach the same store. A key only works on its own store.

## API keys

Create keys in the admin: **חנות > API ומפתחות** (the Super plan and up).

* **Shown once.** The full key (`coi_sk_...`) is shown when it is created and never again - Coi keeps only a hash of it. Copy it into your system right away. Lost it? Delete it and create a new one.
* **Permissions per area.** For products, orders, registered customers and leads a key gets *no access*, *read*, or *read and update*. Give each system only what it needs.
* **Expiry.** No limit, 30 days, 90 days or a year.
* **Up to 5 active keys** per store - one per connected system, so each can be deleted on its own.
* The store's owner gets an email whenever a key is created or deleted.

Send the key in a header on every call:

```bash
curl https://www.your-store.co.il/api/v1/orders?limit=10 \
  -H "Authorization: Bearer coi_sk_..."
```

`X-Api-Key: coi_sk_...` works too. **Never put the key in the URL** - URLs end up in logs. A key sent as `?apikey=` is refused with 400. Keep keys on your server, never in a web page: the API does not answer browsers from other sites (no CORS).

## Requests and answers

Bodies are JSON (`Content-Type: application/json`), in and out. Updates use **PATCH** and change only the fields you send.

A successful answer:

```json
{ "ok": true, "data": { "id": 1647, "name": "..." } }
```

An error, with a real HTTP status:

```json
{ "ok": false, "error": { "code": "invalid_field", "message": "price must be a number." } }
```

| Status | When                                                                                                        |
| ------ | ----------------------------------------------------------------------------------------------------------- |
| 400    | A field or parameter is wrong (`invalid_field`, `invalid_parameter`, `invalid_json`, `nothing_to_update`)   |
| 401    | Missing, invalid, deleted or expired key (`unauthorized`) - always the same answer                          |
| 403    | The key lacks the permission (`insufficient_scope`), or the plan does not include the API (`plan_required`) |
| 404    | No such row in this store, or no such endpoint (`not_found`)                                                |
| 405    | Wrong method for this endpoint; the `Allow` header lists the right ones                                     |
| 409    | A limit or conflict: `plan_limit`, `email_taken`, `registration_disabled`                                   |
| 415    | The body is not JSON (`json_required`)                                                                      |
| 429    | Too many requests (`rate_limited`), or too many failed keys from one IP (`too_many_failures`)               |

Branch on `error.code`, not on the message.

**Formats.** Money is a number with 2 decimals. Dates are ISO 8601 with the offset, in Israel time: `2026-09-27T21:40:00+03:00`. Ids are whole numbers.

## Pagination

Lists come newest first, 50 per page (`limit=1..100`). Each page says whether there is more:

```json
{ "ok": true, "data": [ ... ], "hasMore": true, "nextCursor": "aToxNjQ2" }
```

For the next page, pass `nextCursor` back as `cursor`, as is: `GET /orders?limit=100&cursor=aToxNjQ2`. Stop when `hasMore` is false. A page never skips or repeats rows when new orders arrive in between.

To sync only what changed, use the date filters: `updated_since` on products, `created_since` on orders, customers and leads.

## Rate limit

Up to **120 requests a minute per key**. Every answer carries `RateLimit-Limit`, `RateLimit-Remaining` and `RateLimit-Reset` (seconds to the next minute). Over the limit you get 429 with `Retry-After`. Updating many products? Use **one** [bulk call](/products/batch-update-products) of up to 500 items instead of 500 calls.

## Safe retries

A `POST` that creates something (a product, a customer) takes an `Idempotency-Key` header - any unique string, such as your own record's id. If the connection drops and you send the same request with the same key again within 24 hours, you get the first answer back (header `Idempotent-Replayed: true`) instead of a duplicate.

## Moving from the old product endpoint

The old `/api/product/update?apikey=...` endpoint and the single per-store key are gone. To move:

1. Create a key with **products: read and update** in **API ומפתחות**.
2. Send it in the `Authorization` header.
3. Update one product with [`PATCH /api/v1/products/{id}`](/products/update-product), or many at once with [`POST /api/v1/products/batch`](/products/batch-update-products) - it finds products by `id`, `internalSku` or `sku`, like the old endpoint did.