# HikaShop Connector API

Version 6.6.0. Base path `/hikashop-api/v1`.

The connector plugin ships inside HikaShop Business and answers JSON over HTTPS. The mobile
application is one client of it; anything it does, your own code can do.

Every response carries the same envelope, `{"data": …, "meta": …, "error": null}`, and every route
except `POST /pair` carries a device token as `Authorization: Bearer <token>`. A token holds the
scopes `read` and optionally `write`, and the operator it is bound to keeps their own access
levels, so a device may hold `write` and still be refused an order its operator cannot see.

The base path is a plugin parameter and a shop can move it. Nothing else here changes between
shops.

This file is generated from the plugin's source. It is the whole reference in one flat
file, which is the cheapest form to hand to an assistant; the same content is available as
OpenAPI 3.1 in JSON and YAML beside it.

---

## Pairing a device

A client never sees the merchant's password. It is given a **pairing code** instead, generated in
**System > App Devices** of the backend, and exchanges it once for a token of its own.

The code is nine characters, lives five minutes, may be tried five times before it dies, and can
only be spent once. `POST /pair` is rate limited to thirty attempts per minute per address, which
is the only route that answers without a token.

What comes back is the token, the scopes the code granted, and the id of the device row. The token
is shown once and stored only as a SHA-256 hash, so a device that loses it pairs again rather than
recovering it. Revoking a device is deleting its row in that same screen, and every token it held
stops working at once.

```
curl -X POST "$SHOP/hikashop-api/v1/pair" \
  -H "Content-Type: application/json" \
  -d '{ "code": "K7QM-2F84", "device_name": "Counter tablet" }'

# then, for everything else
curl "$SHOP/hikashop-api/v1/products" \
  -H "Authorization: Bearer $TOKEN"
```

---

## Scopes and access levels

Two separate checks run on every request, and the narrower one wins.

The **scope** is what the device may do at all: `read`, and `write` if the pairing code granted it.
A device without `write` is refused every route that changes anything, whoever holds it.

The **access levels** are the shop's own, belonging to the operator the device is bound to. They
decide which records that person may see and change, exactly as they do in the backend. So a device
holding `write` can still be refused an order, and a device holding only `read` sees a smaller
catalogue than another one.

This is why an integration should be given its own device rather than borrowing one: revoke it and
nothing else is disturbed, and its operator's access levels are the ceiling on what it can reach.

```
{
  "data": null,
  "error": {
    "code": "forbidden",
    "message": "This device does not have the required scope."
  }
}
```

---

## The envelope, errors and paging

Every JSON answer has the same three keys. `data` is the payload, `meta` carries paging and other
context when there is any and is `null` otherwise, and `error` is `null` on success.

On failure `data` is `null` and `error` holds a stable `code` and a human `message`. Read the code,
not the message: the message is written for a person and may be translated or reworded, the code is
what your own logic should branch on.

Listings page with `start` and `limit` in the query, and answer with `start`, `limit` and `total` in
the envelope's `meta`. `total` counts the rows matching the filter before paging, so it is how you
know there is another page. `limit` is capped, usually at a hundred, and asking for more silently
gets you the cap rather than an error.

One thing to note about a listing: `data` is the list itself, not an object wrapping it. The paging
lives in `meta`.

- `invalid_request` (400) A required field is missing or malformed.
- `unauthorized` (401) No token, or a token that is unknown, revoked or unpublished.
- `forbidden` (403) The device lacks the scope, or the operator lacks the access level.
- `not_found` (404) No such record, or no such route. Also returned when the operator may not see the record, so that it cannot be probed for.
- `too_many_requests` (429) Rate limited. Only /pair does this.

```
{
  "data": [ … ],
  "meta": { "start": 0, "limit": 20, "total": 307 },
  "error": null
}
```

---

## Calling it from a browser

The API answers cross-origin requests, because the application is a web application as well as a
phone one. `Access-Control-Allow-Origin` is `*`, the allowed methods are GET, POST, PUT, DELETE and
OPTIONS, and the allowed headers are `Authorization`, `Content-Type` and `X-Hikashop-Token`. A
preflight is answered immediately and may be cached for a day.

`*` with a bearer token is deliberate and safe in the way cookies would not be: nothing is sent
automatically by the browser, so a page on another origin can only call this API if it already
holds a token, and a token is only ever obtained by pairing.

Send the token in the `Authorization` header. Never put it in the query string, where it would be
written to every access log between you and the shop.

```
OPTIONS /hikashop-api/v1/products

Access-Control-Allow-Origin: *
Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS
Access-Control-Allow-Headers: Authorization, Content-Type, X-Hikashop-Token
Access-Control-Max-Age: 86400
```

---

## Adding your own routes

A HikaShop plugin can serve paths of its own through the same base path, the same envelope and the
same authentication, by listening to three events.

`onConnectorBeforeRoute` fires before any matching, so it can intercept a path the connector also
serves. `onConnectorRoute` fires only when nothing matched, which is where a new path belongs.
`onConnectorBeforeResponse` fires just before the JSON is written, for adding a field to an answer
somebody else built.

The listener receives one object by reference, carrying `path` (the part after the base path),
`method`, `base_path`, `response`, and `handled`. Authenticate with `requireScope()` exactly as the
connector's own routes do, answer through `$ctx->response`, and set `$ctx->handled = true` so the
router stops rather than falling through to its 404.

```
public function onConnectorRoute(&$ctx) {
    if ($ctx->path !== 'warehouse/stock' || $ctx->method !== 'GET')
        return;

    $device = HikashopConnectorAuth::requireScope($ctx->response, 'read');
    if ($device === null)
        return;          // it has already answered 401 or 403

    $ctx->response->data(array('pallets' => $this->countPallets()));
    $ctx->handled = true;
}
```

---

## What this shop is

`GET /site` — scope `read`, since 6.6.0

The first call a client makes. It says what it has connected to, who it is connected as, and what
that operator is allowed to do, which is enough to draw an interface without asking for anything
it will only be refused.

Nothing here changes often. Cache it, and use `GET /version` to know when to look again.

### Response

- `app` (string) Always `hikashop-connector`. A cheap way to be sure you are talking to this API and not to something else answering on that path.
- `api_version` (string) Semver of the API itself, not of HikaShop. A minor bump adds fields or routes, a major one breaks something.
- `site_name` (string) What the site calls itself, which is what a merchant recognises the shop by.
- `hikashop_version` (string|null) The HikaShop release running there.
- `cms` (object) What it is running on.
  - `name` (boolean) `joomla` or `wordpress`.
  - `version` (string|null) The CMS release.
- `edition` (string) `starter`, `essential` or `business`. This API only answers on Business, so in practice it is `business` unless the licence has lapsed.
- `logo` (string) The shop's logo as an absolute URL, empty when none is set. The same setting the invoices use.
- `currency` (object) The shop's money.
  - `default` (integer) The currency id, a row in the shop's own table, not an ISO code.
- `price_with_tax` (boolean) Whether prices are shown to customers with tax included. Display only: the prices in this API are as stored.
- `operator` (object) The user the device is bound to.
  - `id` (integer) `0` when the device is bound to nobody, which is a device paired without an operator.
  - `name` (string|null) Their display name.
  - `role` (string) `admin` when they may manage the component, `staff` otherwise.
- `scopes` (string[]) What this device may do: `read`, and `write` when it was granted.
- `permissions` (object) What the operator's access levels allow, keyed by resource (`product`, `order`, `category`, `discount`, `user`, `zone`, `characteristic`, `massaction`, `dashboard`), each with `view`, `manage` and `delete`. Use it to leave controls out rather than showing a screen full of refusals. Its keys are the resources this HikaShop knows about, so an add-on can add one.
- `capabilities` (object) What this shop can do beyond the basics.
  - `pos` (boolean) Reserved for the point of sale, false for now.
  - `push` (boolean) Reserved for push notifications, false for now.
  - `multivendor` (boolean) Whether HikaMarket is installed.

---

## The settings that decide how prices read

`GET /settings` — scope `read`, since 6.6.0

The handful of configuration flags a client needs in order to show the same figures the shop's own
pages show. They are read only here; they are changed in the backend.

### Response

- `product_contact` (boolean) Whether products can be enquired about rather than bought.
- `product_waitlist` (boolean) Whether customers can join a waiting list for something out of stock.
- `price_with_tax` (boolean) Show prices with tax included.
- `floating_tax_prices` (boolean) Whether the tax shown depends on the customer, which means a price cannot be cached across customers.
- `show_original_price` (boolean) Show the price before a discount alongside the discounted one.
- `round_calculations` (integer) When rounding happens during a calculation. Match it or your totals will differ from the shop by a cent.

---

## Change tokens for the cacheable resources

`GET /version` — scope `read`, since 6.6.0

Three opaque tokens, one for each resource worth caching. A token changes when its resource does,
so a client keeps the copy it has until the token moves.

Compare them, do not read them. They are strings today and their meaning is deliberately not part
of this contract.

This is what makes a dictionary of several thousand strings affordable: fetch it once, ask this
cheap route afterwards.

### Response

- `i18n` (string) Moves when the shop's translations change.
- `statuses` (string) Moves when an order status is added, renamed or unpublished.
- `languages` (string) Moves when the shop's languages change.

---

## The shop's order statuses

`GET /statuses` — scope `read`, since 6.6.0

Every published order status, in the order the backend shows them. A shop invents its own, so this
list is not a fixed set and should never be hard coded.

The `namekey` is the identifier to send to `POST /orders/{id}/status`; the `name` is for a person
to read.

### Response

- `namekey` (string) The identifier. This is what you send when changing an order.
- `name` (string) Translated into the operator's language, ready to display.
- `label_key` (string) The translation key behind that name, if you would rather translate it yourself.
- `color` (string) The colour the backend uses for this status, empty when none is set.

---

## HikaShop's own translation of a locale

`GET /i18n` — scope `read`, since 6.6.0

The shop's translation dictionary for one locale, so that a client can name things exactly as the
merchant's own site names them, including whatever they have overridden.

It is large. Cache it against the `i18n` token from `GET /version` rather than fetching it again.

### Query

- `locale` (string) A HikaShop language tag such as `en-GB`. Falls back to the site language when it is not installed.

### Response

- `locale` (string) The locale actually served, which may not be the one asked for.
- `strings` (object) Translation key to translated text. Thousands of keys, and a shop can add or override any of them, so there is no list to give.

---

## The shop's languages

`GET /languages` — scope `read`, since 6.6.0

Which languages the shop publishes, and whether content translation is switched on at all.

When `enabled` is false the shop is single-language and the translation routes have nothing to do.
The default language is marked and comes last, because it is the one already being edited on the
main form rather than a translation of it.

### Response

- `enabled` (boolean) Whether this shop translates its content. False on a single-language site.
- `languages` (object[]) The published languages.
  - `id` (integer) The language id in the shop's table.
  - `code` (string) The tag, such as `fr-FR`.
  - `shortcode` (string) The lower-case underscored form, such as `fr_fr`, which is what the translation tables key on.
  - `site_default` (boolean) True for the language the shop is written in. Its text is the original, not a translation.

---

## Read one product

`GET /products/{id}` — scope `read`, since 6.6.0

Everything the product editor needs in one call: the record, its prices, its images and files, its
categories, its characteristics and variants, and the definitions of the custom fields that apply
to it.

Ask for a variant's id and you get the variant, with `parent_id` set. A parent's `variants` are
listed in full, so you rarely need to.

### Path

- `id` (integer, required) A parent product or a variant.

### Response

- `id` (integer) 
- `name` (string) 
- `code` (string) The SKU. Unique within the shop.
- `description` (string) The long description, as HTML.
- `description_type` (string) Which editor the description was written with.
- `published` (boolean) 
- `quantity` (integer) `-1` when this product does not track stock, which is not the same as `0`.
- `msrp` (number) The manufacturer's suggested price, shown struck through when the shop is configured to.
- `gtin` (string) The barcode: EAN, UPC or ISBN. This is what `GET /products/lookup` matches on.
- `condition` (string) New, used, refurbished. Used by the feeds rather than by the shop itself.
- `weight` (number) Shipping weight, in `weight_unit`.
- `weight_unit` (string) `kg`, `g`, `lb` or `oz`.
- `width` (number) In `dimension_unit`.
- `height` (number) In `dimension_unit`.
- `length` (number) In `dimension_unit`.
- `dimension_unit` (string) `m`, `cm`, `mm`, `ft` or `in`.
- `min_per_order` (integer) The smallest quantity a customer may order, `0` for no minimum.
- `max_per_order` (integer) The largest, `0` for no maximum.
- `sale_start` (integer|null) Unix timestamp before which the product is not on sale.
- `sale_end` (integer|null) Unix timestamp after which it is no longer sold.
- `page_title` (string) SEO title, empty to use the name.
- `meta_description` (string) SEO description.
- `keywords` (string) SEO keywords.
- `canonical` (string) A canonical URL, when this page should point at another.
- `url` (string) The address of the product page on the shop.
- `alias` (string) The slug used in that address.
- `access` (object) Who may see the product: `mode` (`all`, `none` or `groups`) and `groups`, which are user **group** ids and not Joomla view levels. The two id spaces overlap and disagree, so a value that looks plausible can grant the wrong audience.
- `contact` (boolean) Whether this product is enquired about rather than bought.
- `warehouse_id` (integer) The warehouse holding the stock, `0` when the shop has none.
- `type` (string) `main` for a product, `variant` for one of its variants.
- `parent_id` (integer) The parent product when this is a variant, `0` otherwise.
- `value_ids` (integer[]) For a variant, the characteristic values it stands for.
- `manufacturer_id` (integer) The brand, `0` when unset.
- `manufacturer_name` (string) Its name, saving a second call.
- `tax_id` (integer) The tax category, `0` when the product is untaxed.
- `tax_name` (string) Its name.
- `tax_rate` (number) The rate as a fraction, so `0.2` is twenty percent.
- `prices` (object[]) Every price row, including the restricted ones. A product with none is not sellable.
  - `id` (integer) 
  - `value` (number) Tax excluded, as stored.
  - `currency_id` (integer) 
  - `min_quantity` (integer) From how many items this row applies, which is how quantity breaks are expressed.
  - `access` (object) Who this price is for, in the same shape as the product access.
  - `users` (integer[]) Named customers, empty for everyone.
  - `zone_ids` (integer[]) Zones this price applies in, empty for everywhere.
  - `start_date` (integer|null) Unix timestamp.
  - `end_date` (integer|null) Unix timestamp.
- `images` (object[]) In the order the editor shows them; the first is the main image.
  - `id` (integer) 
  - `name` (string) 
  - `path` (string) Relative to the upload folder.
  - `url` (string) Absolute, ready to display.
  - `ordering` (integer) 
  - `description` (string) The alt text.
  - `access` (object) Who may see it.
  - `free_download` (boolean) Files only; meaningless on an image.
- `files` (object[]) Downloadable files, in the same shape as the images.
  - `id` (integer) 
  - `name` (string) 
  - `path` (string) Relative to the upload folder.
  - `url` (string) Absolute.
  - `ordering` (integer) 
  - `description` (string) 
  - `access` (object) Who may download it.
  - `free_download` (boolean) Whether it can be downloaded without buying the product.
- `categories` (object[]) The categories the product is in.
  - `id` (integer) 
  - `name` (string) 
- `bundle` (object[]) The products this one is made of, when it is a bundle.
  - `id` (integer) 
  - `name` (string) 
  - `code` (string) 
  - `quantity` (integer) How many of it the bundle contains.
- `options` (object[]) Products offered as options alongside this one.
  - `id` (integer) 
  - `name` (string) 
  - `code` (string) 
  - `quantity` (integer) 
- `related` (object[]) Products shown as related.
  - `id` (integer) 
  - `name` (string) 
  - `code` (string) 
  - `quantity` (integer) 
- `tags` (integer[]) CMS tag ids.
- `characteristics` (object[]) The characteristics this product varies on. Empty when it has no variants.
- `variants` (object[]) Every variant, with its own code, stock, price and images. Empty for a product that does not vary.
- `fields` (object[]) The definitions of the custom fields that apply to this product, so a client can build a form for them.
- `custom_fields` (object) Their values, keyed by namekey. The keys are whatever this shop has configured; `fields` in the same response says what they are.
- `custom_field_files` (object) For custom fields holding a file, the file behind each value. Keyed the same way.
  - `mode` (string) `all`, `none`, or `groups` when it is restricted to some.
  - `groups` (integer[]) User group ids, meaningful only when the mode is `groups`.
    - `mode` (string) As above.
    - `groups` (integer[]) As above.
    - `mode` (string) As above.
    - `groups` (integer[]) As above.
  - `id` (integer) The characteristic, such as Size.
  - `name` (string) Its name.
  - `values` (object[]) The values of it this product uses, such as S, M and L.
    - `id` (integer) The value id, which is what a variant refers to.
    - `value` (string) Its name, such as `M`.
  - `id` (integer) The variant is a product in its own right, and this is its id.
  - `code` (string) Its own SKU.
  - `quantity` (integer) Its own stock. This is the figure to change, not the parent's.
  - `published` (boolean) 
  - `price` (number|null) `null` when the variant has no price of its own and the parent's applies.
  - `values` (object[]) Which characteristic values this variant stands for, one per characteristic.
    - `option_id` (integer) The characteristic.
    - `option_name` (string) Its name, so the variant can be labelled without a second call.
    - `value_id` (integer) The value.
    - `value` (string) Its name.
  - `images` (object[]) The variant's own images, in the same shape as the product's.
    - `id` (integer) 
    - `name` (string) 
    - `path` (string) Relative to the upload folder.
    - `url` (string) Absolute.
    - `ordering` (integer) 
  - `namekey` (string) The key used in `custom_fields`.
  - `type` (string) `text`, `radio`, `singledropdown`, `file`, and the rest of HikaShop's field types.
  - `raw_type` (string) HikaShop's own name for the type, before it is mapped to something a client can render.
  - `label` (string) Translated into the operator's language.
  - `default` (string) The value used when none is given.
  - `required` (boolean) Whether the shop refuses to save the product without it.
  - `options` (object[]) The choices, for a field that has them. Empty for a free text one.
  - `multiple` (boolean) Whether more than one choice may be selected.
  - `translatable` (boolean) Whether its value can be translated, which is what the translation routes offer.
  - `upload_dir` (string) For a file field, where its uploads are kept.
  - `allowed_extensions` (string) For a file field, the extensions it accepts, comma separated. Empty means the shop default.
  - `date_format` (string) For a date field, the format it is stored in.

---

## Resolve a scanned barcode

`GET /products/lookup` — scope `read`, since 6.6.0

One product from one barcode, for a scanner. It matches the product code and the GTIN, and it
looks at variants as well as parents, because the thing with a barcode on it is usually the
variant.

Exactly one product comes back, or `not_found`. It is deliberately not a search: a scanner needs an
answer, not a list.

### Query

- `barcode` (string) What the scanner read. Matched against the product code and the GTIN.

### Response

- `id` (integer) The product to open. For a variant, this is its parent.
- `variant_id` (integer) The variant that was scanned, `0` when the barcode belonged to the parent.
- `name` (string) 
- `code` (string) The SKU that matched.
- `gtin` (string) The barcode held on the record, which may differ in leading zeros from what was scanned.
- `quantity` (integer) The stock of whichever record matched, so a stock take needs no second call.

### Errors

- `missing_barcode` (400) No barcode was given.
- `not_found` (404) Nothing in the shop carries that code.

---

## Products running out

`GET /products/low-stock` — scope `read`, since 6.6.0

Sellable items at or below a stock threshold, cheapest first to reorder. Variants are listed in
their own right, since that is where the stock actually sits.

Products that do not track stock are left out: they are not running out of anything.

### Query

- `threshold` (integer) At or below which a product counts as low. Defaults to `5`.
- `limit` (integer) Defaults to `20`, capped at `100`.

### Response

- `id` (integer) The parent product.
- `variant_id` (integer) The variant that is low, `0` when the parent itself is.
- `name` (string) 
- `code` (string) The SKU of whichever record is low.
- `quantity` (integer) What is left.

---

## Set a product's stock

`POST /products/{id}/stock` — scope `write`, since 6.6.0

Sets the tracked quantity to an absolute figure, which is what a stock take does. It is not an
adjustment: send what the shelf holds, not the difference.

A product with variants keeps no stock of its own, so set it on the variant instead. Asking to set
it on the parent is refused rather than silently ignored.

### Path

- `id` (integer, required) The product or variant to set.

### Body

- `quantity` (integer, required) The new absolute quantity. `-1` turns stock tracking off for this product.

### Response

- `id` (integer) 
- `quantity` (integer) As stored, so you can confirm what was written.

### Errors

- `not_found` (404) No such product, or the operator may not change it.
- `has_variants` (409) It is a parent: set the stock on one of its variants.

---

## List customers

`GET /customers` — scope `read`, since 6.6.0

The customers the operator may see, with enough to recognise one and to know whether they have
bought anything.

Guests are included. A shop that lets people order without an account still has a row for each of
them, and `type` is how you tell the two apart.

### Query

- `start` (integer) Offset. Defaults to `0`.
- `limit` (integer) Defaults to `20`, capped at `100`.
- `search` (string) Matches the email address, the account name and login, and the first name and surname on any of their addresses, including the two together, so `John Doe` finds a guest who has never had an account.

### Response

- `id` (integer) The HikaShop customer id, which is not the CMS user id.
- `name` (string) The account name, or for a guest the name on their default address, since a guest has no account to take one from.
- `email` (string) 
- `type` (string) `registered` for an account, `guest` for someone who ordered without one.
- `created` (integer) Unix timestamp of the first time the shop saw them.
- `order_count` (integer) How many orders they have placed, so a list can be sorted by worth without a second call.

---

## List discounts and coupons

`GET /discounts` — scope `read`, since 6.6.0

Both kinds of reduction live in one table and are told apart by `type`: a **discount** applies by
itself when its conditions are met, a **coupon** waits for its code to be entered.

The restriction fields are the interesting part, and they are all lists of ids: which products,
which categories, which zones, which customers, and the same again for exclusions. An empty list
means no restriction of that kind rather than none allowed.

### Query

- `start` (integer) Offset. Defaults to `0`.
- `limit` (integer) Defaults to `20`, capped at `100`.
- `type` (string) `discount` or `coupon`, to list one kind.
- `search` (string) Matches the code.

### Response

- `id` (integer) 
- `type` (string) `discount` applies by itself, `coupon` waits for its code.
- `code` (string) What the customer types. Empty on a discount.
- `kind` (string) Whether the value is a percentage or a fixed amount.
- `value` (number) The reduction, read according to `kind`.
- `currency_id` (integer) The currency a fixed amount is in.
- `published` (boolean) 
- `start` (integer|null) Unix timestamp before which it does not apply.
- `end` (integer|null) Unix timestamp after which it expires.
- `minimum_order` (number) Order total below which it does not apply, `0` for none.
- `maximum_order` (number) Order total above which it stops applying, `0` for none.
- `quota` (integer) How many times it may be used in total, `0` for no limit.
- `quota_per_user` (integer) How many times one customer may use it, `0` for no limit.
- `used_times` (integer) How many times it already has been.
- `tax_included` (boolean) Whether the value is understood as tax included.
- `tax_id` (integer) The tax category of the reduction itself.
- `shipping_percent` (number) A reduction on the shipping rather than on the goods.
- `minimum_products` (integer) Fewest items in the cart for it to apply.
- `maximum_products` (integer) Most items for it to still apply.
- `product_ids` (integer[]) Restricted to these products. Empty means all of them.
- `exclude_product_ids` (integer[]) Never applies to these.
- `category_ids` (integer[]) Restricted to these categories.
- `category_childs` (boolean) Whether those categories include their sub-categories.
- `exclude_category_ids` (integer[]) Never applies in these categories.
- `exclude_category_childs` (boolean) Whether those exclusions include sub-categories.
- `zone_ids` (integer[]) Restricted to these zones.
- `user_ids` (integer[]) Restricted to these customers.
- `access` (object) Which user groups it is for, in the usual `mode` and `groups` shape.
  - `mode` (string) `all`, `none`, or `groups` when it is restricted to some.
  - `groups` (integer[]) User **group** ids, and not Joomla view levels. The two id spaces overlap and disagree, so a value that looks plausible can grant the wrong audience.
- `exclude_access` (object) Which user groups it is never for.
  - `mode` (string) `all`, `none`, or `groups` when it is restricted to some.
  - `groups` (integer[]) User **group** ids, and not Joomla view levels. The two id spaces overlap and disagree, so a value that looks plausible can grant the wrong audience.
- `auto_load` (boolean) For a coupon, whether the shop applies it without the customer typing it.
- `product_only` (boolean) Whether it reduces only the goods and leaves the fees alone.
- `discounted_products` (integer) How many products in the cart it applied to, on a discount that has been used.

---

## The category tree

`GET /categories` — scope `read`, since 6.6.0

One level of the category tree at a time, unpublished categories included, which is what makes this
a management view rather than a shop one.

Pass a `parent_id` to walk down. `has_children` tells you whether there is anything below without
asking, so a tree can be drawn lazily.

HikaShop keeps its manufacturers and its tax categories in the same table as the product
categories, told apart by their root. Ask for the tree you want.

### Query

- `parent_id` (integer) Whose children to list. Omit for the top level.
- `start` (integer) Offset. Defaults to `0`.
- `limit` (integer) Defaults to `20`, capped at `100`.
- `search` (string) Matches the name, across the whole tree rather than one level.

### Response

- `id` (integer) 
- `name` (string) 
- `parent_id` (integer) Its parent, so a flat answer can be rebuilt into a tree.
- `published` (boolean) 
- `has_children` (boolean) Whether anything sits below it.
- `image` (string|null) Absolute URL of its image, `null` when it has none.

---

## Read one category

`GET /categories/{id}` — scope `read`, since 6.6.0

A category with its description, its image, who may see it, and your own category fields.

### Path

- `id` (integer, required) The category id.

### Response

- `fields` (object[]) The definitions of your own category fields.
- `id` (integer) 
- `name` (string) 
- `parent_id` (integer) Its parent.
- `type` (string) Which tree it belongs to: `product`, `manufacturer`, `tax`, and so on.
- `description` (string) The long description, as HTML.
- `meta_description` (string) SEO description.
- `published` (boolean) 
- `access` (object) Who may see it.
- `image` (string|null) Absolute URL of its image, `null` when it has none.
- `custom_fields` (object) Their values, keyed by namekey. The keys depend on the shop; `fields` says what they are.
- `custom_field_files` (object) For a field holding a file, the file behind the value. Keyed the same way.
  - `mode` (string) `all`, `none`, or `groups` when it is restricted to some.
  - `groups` (integer[]) User **group** ids, and not Joomla view levels. The two id spaces overlap and disagree, so a value that looks plausible can grant the wrong audience.

---

## The shop's own bulk operations

`GET /massactions` — scope `read`, since 6.6.0

The mass actions the merchant has configured for a listing, so a client can offer the merchant's own
bulk operations rather than a fixed set of its own.

Whatever they built in the backend appears here, and running one is `POST /massactions/{id}`.

### Query

- `table` (string) Which listing: `product`, `order`, `user`, `category`, `address`.

### Response

- `id` (integer) What to run.
- `name` (string) As the merchant named it.
- `description` (string) Their own note about what it does, when they wrote one.
- `table` (string) The listing it belongs to.
- `restricted` (boolean) Whether it may only run on a selection rather than on everything matching a filter.

### Errors

- `invalid_request` (400) The table is not one the shop has mass actions for.
- `forbidden` (403) The operator may not view that kind of record.

---

## Browse the upload folder

`GET /media/browse` — scope `read`, since 6.6.0

Folders and images inside the shop's upload folder, so an existing image can be picked instead of
uploaded again.

Paths are relative to that folder and never escape it: a path pointing outside is refused rather
than resolved.

### Query

- `folder` (string) Which folder to list, relative to the upload folder. Omit for its root.
- `offset` (integer) Offset into the images, for a folder with many.

### Response

- `folder` (string) The folder being listed.
- `parent` (string) The folder above, for walking back up.
- `has_parent` (boolean) False at the root, where there is nothing above.
- `folders` (object[]) The folders inside it.
- `images` (object[]) The images inside it.
  - `name` (string) The file name.
  - `path` (string) Relative to the upload folder, which is what a product image stores.
  - `url` (string) Absolute, ready to display.
- `total` (integer) How many images the folder holds in all.
- `offset` (integer) Echoes the offset used.

### Errors

- `not_found` (404) No such folder, or a path that tried to leave the upload folder.

---

## Search countries, states and zones

`GET /zones` — scope `read`, since 6.6.0

The shop's zones, which are its countries, its states and any grouping the merchant made of them.
Meant for filling a picker: search for a name, or resolve ids you already hold.

An address stores a zone by its namekey rather than its id, which is why both come back.

### Query

- `search` (string) Matches the name.
- `ids` (string) Comma separated zone ids, to resolve a known set.
- `type` (string) Restrict to `country`, `state` or a zone group.

### Response

- `id` (integer) 
- `namekey` (string) What an address stores, such as `FRA` or `US-CA`.
- `name` (string) Translated where the shop has a translation for it.
- `type` (string) `country`, `state`, or the kind of grouping it is.

---

## Search customers for a picker

`GET /users` — scope `read`, since 6.6.0

A short list of customers matching a search, for the pickers that restrict a price or a discount to
named people. `GET /customers` is the listing to use for anything else.

### Query

- `search` (string) Matches the name and the email address.
- `ids` (string) Comma separated ids, to resolve a known set.

### Response

- `id` (integer) The customer id, which is what a restriction stores.
- `name` (string) 
- `email` (string) Enough to tell two people of the same name apart.

---

## Reference data for the product editor

`GET /products/meta` — scope `read`, since 6.6.0

Everything a product form needs to offer choices: the currencies with their formatting, the tax
categories, the characteristics and their values, the units, the field definitions, the warehouses
and the tags.

One call rather than eight, and it changes rarely, so cache it.

### Response

- `currencies` (object[]) Every published currency, with enough to format an amount the way the shop does.
  - `id` (integer) What a price stores.
  - `code` (string) The ISO code, such as `EUR`.
  - `symbol` (string) 
  - `name` (string) 
  - `decimals` (integer) How many decimal places to show.
  - `decimal_sep` (string) The decimal separator.
  - `thousands_sep` (string) The thousands separator.
  - `symbol_before` (boolean) Whether the symbol goes before the figure.
  - `space` (boolean) Whether a space separates the symbol from the figure.
  - `rounding_increment` (number) What amounts are rounded to, for a currency without small coins.
- `main_currency_id` (integer) The shop's own currency.
- `tax_categories` (object[]) The tax categories a product can be put in.
- `characteristics` (object[]) Every characteristic in the shop, with its values, for building variants.
  - `id` (integer) 
  - `name` (string) 
  - `values` (object[]) Its values.
- `weight_units` (string[]) The weight units the shop accepts, such as `kg`.
- `dimension_units` (string[]) The dimension units the shop accepts, such as `m`.
- `product_fields` (object[]) The definitions of your own product fields.
- `category_fields` (object[]) The definitions of your own category fields.
- `bundle_supported` (boolean) Whether this edition can sell bundles.
- `warehouses` (object[]) The warehouses stock can be held in. Empty when the shop has none.
  - `id` (integer) What `warehouse_id` on a product stores.
  - `name` (string) 
- `tags` (object[]) The CMS tags a product can carry.
  - `id` (integer) 
  - `name` (string) 
  - `parent_id` (integer) Tags are a tree in both Joomla and WordPress.
  - `id` (integer) What `tax_id` on a product stores.
  - `name` (string) 
  - `parent_id` (integer) They live in the category tree, so they have a parent like anything else there.
    - `id` (integer) What a variant refers to.
    - `value` (string) Its name, such as `M`.
  - `namekey` (string) The key its value is stored under.
  - `type` (string) What to render: `text`, `radio`, `singledropdown`, `file`, and the rest.
  - `raw_type` (string) HikaShop's own name for the type.
  - `label` (string) Translated into the operator's language.
  - `default` (string) The value used when none is given.
  - `required` (boolean) Whether the shop refuses to save without it.
  - `options` (object[]) The choices, for a field that has them.
  - `multiple` (boolean) Whether more than one may be chosen.
  - `translatable` (boolean) Whether its value can be translated.
  - `upload_dir` (string) For a file field, where its uploads are kept.
  - `allowed_extensions` (string) For a file field, the extensions it accepts. Empty means the shop default.
  - `date_format` (string) For a date field, the format it is stored in.
    - `value` (string) What to send back when this choice is picked.
    - `label` (string) What to show.
    - `label_key` (string) The translation key behind the label, when there is one.
  - `namekey` (string) The key its value is stored under.
  - `label` (string) Translated into the operator's language.
  - `type` (string) What to render: `text`, `radio`, `singledropdown`, `file`, and the rest.
  - `raw_type` (string) HikaShop's own name for the type.
  - `required` (boolean) Whether the shop refuses to save without it.
  - `default` (string) The value used when none is given.
  - `options` (object[]) The choices, for a field that has them.
    - `value` (string) What to send back when this choice is picked.
    - `label` (string) What to show.
    - `label_key` (string) The translation key behind the label, when there is one.
  - `multiple` (boolean) Whether more than one may be chosen.
  - `translatable` (boolean) Whether its value can be translated.
  - `upload_dir` (string) For a file field, where its uploads are kept.
  - `allowed_extensions` (string) For a file field, the extensions it accepts. Empty means the shop default.
  - `date_format` (string) For a date field, the format it is stored in.

---

## Replace a product's prices

`PUT /products/{id}/prices` — scope `write`, since 6.6.0

Replaces the whole price set, it does not merge into it. Send every price the product should have,
including the ones you are not changing; anything you leave out is deleted.

That is deliberate, because a price set is a set: quantity breaks and audience restrictions only
make sense against each other.

### Path

- `id` (integer, required) The product or variant.

### Body

- `prices` (object[], required) The complete set. Each needs at least a `value` and a `currency_id`; `min_quantity`, `access`, `users`, `zone_ids`, `start_date` and `end_date` are optional and default to no restriction.

### Response

- `id` (integer) 
- `value` (number) Tax excluded, as stored.
- `currency_id` (integer) 
- `min_quantity` (integer) From how many items this row applies, which is how a quantity break is expressed.
- `access` (object) Which user groups the price is for.
- `users` (integer[]) Named customers, empty for everyone.
- `zone_ids` (integer[]) Zones it applies in, empty for everywhere.
- `start_date` (integer|null) Unix timestamp.
- `end_date` (integer|null) Unix timestamp.
  - `mode` (string) `all`, `none` or `groups`.
  - `groups` (integer[]) User group ids, not view levels.

### Errors

- `invalid_request` (400) No prices array was sent.
- `invalid_price` (400) A price in the set cannot be stored, and the message says which one and why. Nothing is saved: a set is replaced whole or not at all.
- `not_found` (404) No such product, or the operator may not change it.

---

## Replace a product's categories

`PUT /products/{id}/categories` — scope `write`, since 6.6.0

Replaces the set of categories the product is in. As with the prices, send the complete set: what
you leave out is removed.

### Path

- `id` (integer, required) The product.

### Body

- `categories` (integer[], required) The complete set of category ids. An empty array takes the product out of every category, which hides it from the shop.

### Response

- `id` (integer) The category.
- `name` (string) Its name, so a client need not look it up.

### Errors

- `not_found` (404) No such product, or the operator may not change it.

---

## A product's translations

`GET /products/{id}/translations` — scope `read`, since 6.6.0

Which of a record's texts can be translated, what they say now in each language, and what the
original says. `enabled` is false on a single-language shop, where there is nothing to do.

`columns` is the list of translatable things: HikaShop's own (the name, the description, the SEO
texts) plus every custom field the merchant flagged translatable. It is built the same way the
backend builds it, so what you are offered is exactly what the shop will store.

The labels and values of a custom field itself are website configuration and are not offered here;
they belong in the backend.

### Path

- `id` (integer, required) The product.

### Response

- `enabled` (boolean) False on a single-language shop, where the rest is empty.
- `languages` (object[]) The shop's languages. The default one is marked and comes last, because its text is the original rather than a translation of it.
- `columns` (object[]) What can be translated on this record: HikaShop's own texts plus every custom field flagged translatable.
- `values` (object) What each language says now, keyed by language code and then by column. Keyed by language code, and within that by column name, both of which depend on the shop.
- `original` (object) What the record itself says, keyed by column, so a translator can see what they are translating. Keyed by column name, which depends on the fields this shop has.
  - `id` (integer) 
  - `code` (string) The tag, such as `fr-FR`.
  - `shortcode` (string) The lower-case underscored form the translation tables key on.
  - `site_default` (boolean) True for the language the shop is written in.
  - `name` (string) The column name, such as `product_name`. This is the key to send a translation under.
  - `type` (string) `text` for a line, `textarea` for prose, so a client knows which control to draw.

### Errors

- `not_found` (404) No such record, or the operator may not see it.
- `translation_disabled` (400) This shop does not translate content.

---

## A category's translations

`GET /categories/{id}/translations` — scope `read`, since 6.6.0

The same as for a product, for a category: its name, its description, its SEO texts and its
translatable custom fields.

### Path

- `id` (integer, required) The category.

### Response

- `enabled` (boolean) False on a single-language shop, where the rest is empty.
- `languages` (object[]) The shop's languages. The default one is marked and comes last, because its text is the original rather than a translation of it.
- `columns` (object[]) What can be translated on this record: HikaShop's own texts plus every custom field flagged translatable.
- `values` (object) What each language says now, keyed by language code and then by column. Keyed by language code, and within that by column name, both of which depend on the shop.
- `original` (object) What the record itself says, keyed by column, so a translator can see what they are translating. Keyed by column name, which depends on the fields this shop has.
  - `id` (integer) 
  - `code` (string) The tag, such as `fr-FR`.
  - `shortcode` (string) The lower-case underscored form the translation tables key on.
  - `site_default` (boolean) True for the language the shop is written in.
  - `name` (string) The column name, such as `product_name`. This is the key to send a translation under.
  - `type` (string) `text` for a line, `textarea` for prose, so a client knows which control to draw.

### Errors

- `not_found` (404) No such record, or the operator may not see it.
- `translation_disabled` (400) This shop does not translate content.

---

## Save a product's translations

`PUT /products/{id}/translations` — scope `write`, since 6.6.0

Writes the translations of one record. Send only the languages you changed; the rest are left alone.

Where the shop uses Falang, the values go into its tables. Where it does not, they become language
overrides keyed on the original text, which is why renaming a product re-keys its translations
rather than losing them.

### Path

- `id` (integer, required) The product.

### Body

An object keyed by language, each holding an object of column name to text:

`{ "2": { "product_name": "Lampe de bureau", "product_description": "…" } }`

The keys are the language ids from `languages` in the GET, and the column names are the ones from
`columns`. A language you do not send is left alone, and so is a column you do not send within a
language you do.

### Response

- `id` (integer) The record that was saved.
- `saved` (integer) How many languages were written, so a client can tell that something was actually stored.

### Errors

- `not_found` (404) No such record, or the operator may not change it.
- `translation_disabled` (400) This shop does not translate content.

---

## Save a category's translations

`PUT /categories/{id}/translations` — scope `write`, since 6.6.0

The same as for a product, for a category.

### Path

- `id` (integer, required) The category.

### Body

An object keyed by language, each holding an object of column name to text:

`{ "2": { "product_name": "Lampe de bureau", "product_description": "…" } }`

The keys are the language ids from `languages` in the GET, and the column names are the ones from
`columns`. A language you do not send is left alone, and so is a column you do not send within a
language you do.

### Response

- `id` (integer) The record that was saved.
- `saved` (integer) How many languages were written.

### Errors

- `not_found` (404) No such record, or the operator may not change it.
- `translation_disabled` (400) This shop does not translate content.

---

## Dashboard figures

`GET /stats/dashboard` — scope `read`, since 6.6.0

Revenue, orders, average basket and new customers over a period, the same figures again for the
period before it so a change can be shown, the series behind them, and the best sellers.

Only orders the shop counts as sold are included, so a cancelled order does not inflate a total.
Amounts are in the shop's own currency.

### Query

- `range` (string) `day`, `week`, `month` or `year`. Defaults to `month`.

### Response

- `range` (string) The period the figures cover.
- `currency_id` (integer) The shop's currency, which every amount here is in.
- `totals` (object) The four headline figures.
  - `revenue` (number) Taken in the period.
  - `orders` (integer) How many were placed.
  - `average_order` (number) Revenue divided by orders.
  - `customers` (integer) New customers in the period.
- `previous` (object) The same four figures for the preceding period of the same length, for a comparison.
- `series_granularity` (string) Whether the series is by day, week, month or year, which follows from the range.
- `revenue_series` (object[]) One point per interval, for a chart.
  - `date` (string) The interval, as a date.
  - `revenue` (number) Taken in it.
- `top_products` (object[]) The best sellers of the period.
  - `name` (string) 
  - `quantity` (integer) How many were sold.
  - `revenue` (number) Taken in the preceding period.
  - `orders` (integer) Placed in it.
  - `average_order` (number) Its average basket.
  - `customers` (integer) New customers in it.

---

## The user groups

`GET /groups` — scope `read`, since 6.6.0

The CMS user groups, for the pickers that restrict a price, a discount or a product to some of
them. The list is the same shape on Joomla and on WordPress, which is the point of it: a client does
not need to know which it is talking to.

These are the ids an `access` object holds. They are user groups and not Joomla view levels.

### Response

- `id` (integer) What an access object stores.
- `title` (string) The name of the group.

---

## Price a product in an order

`GET /orders/{id}/products/precompute` — scope `read`, since 6.6.0

What a product would cost on this order, before adding it. The order's customer, currency and zone
all bear on the price, so this asks the shop rather than guessing from the catalogue.

Use it to show a line before it is committed, then `POST /orders/{id}/products` to add it.

### Path

- `id` (integer, required) The order.

### Query

- `product_id` (integer) The product or variant to price.
- `quantity` (integer) How many, which matters where the product has quantity breaks. Defaults to `1`.

### Response

- `product_id` (integer) What was priced.
- `name` (string) 
- `code` (string) Its SKU.
- `quantity` (integer) The quantity the price was worked out for.
- `price` (number) Unit price, tax excluded, in the order currency.
- `tax` (number) Tax per unit, worked out for this order rather than in general.
- `tax_namekeys` (string[]) Which tax rates that came from.

### Errors

- `not_found` (404) No such order or no such product, or the operator may not see them.

---

## Read one customer

`GET /customers/{id}` — scope `read`, since 6.6.0

A customer with their addresses, their orders, which groups they are in and your own customer
fields.

`can_edit_account` and `groups_editable` are worth reading before drawing a form: they say whether
this operator may change this particular account at all, so a client can leave the controls out
rather than offer something that will be refused. A customer can have many addresses and one
default of each kind.

### Path

- `id` (integer, required) The customer id.

### Response

- `id` (integer) The HikaShop customer id, which is what every customer route takes.
- `cms_id` (integer) The Joomla or WordPress user id, `0` for a guest with no account.
- `name` (string) 
- `email` (string) 
- `username` (string) The login, empty for a guest.
- `type` (string) `registered` or `guest`.
- `blocked` (boolean) Whether the CMS account is disabled.
- `can_edit_account` (boolean) Whether this operator may change the login and password of this particular account. False for an account above their own level, which is how a super user is protected from staff.
- `groups_editable` (boolean) Whether this operator may change which groups the customer is in.
- `groups` (object[]) The groups they are in.
  - `id` (integer) 
  - `title` (string) 
- `available_groups` (object[]) Every group, with whether this operator may put the customer into it, so a picker can grey out the rest rather than offering a refusal.
  - `id` (integer) 
  - `title` (string) 
  - `assignable` (boolean) False for a group this operator may not grant.
- `created` (integer) Unix timestamp of the first time the shop saw them.
- `addresses` (object[]) Their addresses, defaults first.
  - `id` (integer) 
  - `types` (string[]) Which of `billing` and `shipping` it is used for.
  - `name` (string) 
  - `company` (string) 
  - `street` (string) 
  - `city` (string) 
  - `post_code` (string) 
  - `telephone` (string) 
  - `default` (boolean) Whether it is the default for one of its types.
  - `formatted` (object) The address laid out the way this shop lays addresses out, which depends on its address format setting.
- `orders` (object[]) Their orders, newest first, enough to list them.
  - `id` (integer) 
  - `number` (string) The number the customer sees.
  - `status` (string) A namekey.
  - `created` (integer) Unix timestamp.
  - `total` (number) Tax included, in the order currency.
  - `currency_id` (integer) That currency.
- `fields` (object[]) The definitions of your own customer fields.
- `custom_fields` (object) Their values, keyed by namekey. The keys depend on the shop; `fields` says what they are.
- `custom_field_files` (object) For a field holding a file, the file behind the value. Keyed the same way.
    - `text` (string) Several lines, for an invoice or a label.
    - `one_line` (string) One line, for a list.

### Errors

- `not_found` (404) No such customer, or the operator may not see them.

---

## Delete a customer

`DELETE /customers/{id}` — scope `write`, since 6.6.0

Removes a customer and their addresses.

A customer with orders is refused, which is what the backend does too: delete their orders first,
and then the customer.

### Path

- `id` (integer, required) The customer id.

### Response

- `deleted` (boolean) True when the row is gone.

### Errors

- `not_found` (404) No such customer, or the operator may not delete them.
- `has_orders` (400) They have orders. Delete those first.
- `delete_failed` (400) The shop refused to delete the row.

---

## Update a customer's profile

`PUT /customers/{id}` — scope `write`, since 6.6.0

Changes the name, the email address, the login, the password, the user groups and your own customer
fields, and answers with the customer as it now stands.

The password is written straight into the CMS account and is never returned by anything. Changing
the email of a registered customer changes the account they log in with, which is why a clash is
refused rather than silently ignored.

### Path

- `id` (integer, required) The customer id.

### Body

- `name` (string) Their display name.
- `email` (string) Must not belong to another account.
- `username` (string) The login, for a registered customer.
- `password` (string) A new password. Send it only when changing it.
- `groups` (integer[]) The user groups they belong to, as ids from `GET /groups`.
- `custom_fields` (object) Your own customer fields, keyed by namekey.

### Response

- `id` (integer) The HikaShop customer id, which is what every customer route takes.
- `cms_id` (integer) The Joomla or WordPress user id, `0` for a guest with no account.
- `name` (string) 
- `email` (string) 
- `username` (string) The login, empty for a guest.
- `type` (string) `registered` or `guest`.
- `blocked` (boolean) Whether the CMS account is disabled.
- `can_edit_account` (boolean) Whether this operator may change the login and password of this particular account. False for an account above their own level, which is how a super user is protected from staff.
- `groups_editable` (boolean) Whether this operator may change which groups the customer is in.
- `groups` (object[]) The groups they are in.
  - `id` (integer) 
  - `title` (string) 
- `available_groups` (object[]) Every group, with whether this operator may put the customer into it, so a picker can grey out the rest rather than offering a refusal.
  - `id` (integer) 
  - `title` (string) 
  - `assignable` (boolean) False for a group this operator may not grant.
- `created` (integer) Unix timestamp of the first time the shop saw them.
- `addresses` (object[]) Their addresses, defaults first.
  - `id` (integer) 
  - `types` (string[]) Which of `billing` and `shipping` it is used for.
  - `name` (string) 
  - `company` (string) 
  - `street` (string) 
  - `city` (string) 
  - `post_code` (string) 
  - `telephone` (string) 
  - `default` (boolean) Whether it is the default for one of its types.
  - `formatted` (object) The address laid out the way this shop lays addresses out, which depends on its address format setting.
- `orders` (object[]) Their orders, newest first, enough to list them.
  - `id` (integer) 
  - `number` (string) The number the customer sees.
  - `status` (string) A namekey.
  - `created` (integer) Unix timestamp.
  - `total` (number) Tax included, in the order currency.
  - `currency_id` (integer) That currency.
- `fields` (object[]) The definitions of your own customer fields.
- `custom_fields` (object) Their values, keyed by namekey. The keys depend on the shop; `fields` says what they are.
- `custom_field_files` (object) For a field holding a file, the file behind the value. Keyed the same way.
    - `text` (string) Several lines, for an invoice or a label.
    - `one_line` (string) One line, for a list.

### Errors

- `not_found` (404) No such customer, or the operator may not change them.
- `invalid_email` (400) The email address is not one.
- `forbidden_target` (400) The operator may not change this particular account, which is how a super user is protected from being edited by staff.
- `username_taken` (400) Another account has that login.
- `email_taken` (400) Another account has that email address.
- `account_save_failed` (400) The CMS refused to save the account.
- `invalid_fields` (400) One of your own fields was rejected by its own rules.

---

## Give a guest an account

`POST /customers/{id}/account` — scope `write`, since 6.6.0

Turns a guest into a registered customer, keeping their orders and addresses, and answers with the
customer as it now stands.

This is the useful direction: someone ordered without an account, then asked for one. Creating the
account separately would leave their history behind.

### Path

- `id` (integer, required) The guest customer.

### Body

- `username` (string, required) The login to create.
- `password` (string, required) Their password. Never returned by anything afterwards.
- `name` (string) Their display name, defaulting to the one on the guest record.
- `groups` (integer[]) The user groups to put them in.

### Response

- `id` (integer) The HikaShop customer id, which is what every customer route takes.
- `cms_id` (integer) The Joomla or WordPress user id, `0` for a guest with no account.
- `name` (string) 
- `email` (string) 
- `username` (string) The login, empty for a guest.
- `type` (string) `registered` or `guest`.
- `blocked` (boolean) Whether the CMS account is disabled.
- `can_edit_account` (boolean) Whether this operator may change the login and password of this particular account. False for an account above their own level, which is how a super user is protected from staff.
- `groups_editable` (boolean) Whether this operator may change which groups the customer is in.
- `groups` (object[]) The groups they are in.
  - `id` (integer) 
  - `title` (string) 
- `available_groups` (object[]) Every group, with whether this operator may put the customer into it, so a picker can grey out the rest rather than offering a refusal.
  - `id` (integer) 
  - `title` (string) 
  - `assignable` (boolean) False for a group this operator may not grant.
- `created` (integer) Unix timestamp of the first time the shop saw them.
- `addresses` (object[]) Their addresses, defaults first.
  - `id` (integer) 
  - `types` (string[]) Which of `billing` and `shipping` it is used for.
  - `name` (string) 
  - `company` (string) 
  - `street` (string) 
  - `city` (string) 
  - `post_code` (string) 
  - `telephone` (string) 
  - `default` (boolean) Whether it is the default for one of its types.
  - `formatted` (object) The address laid out the way this shop lays addresses out, which depends on its address format setting.
- `orders` (object[]) Their orders, newest first, enough to list them.
  - `id` (integer) 
  - `number` (string) The number the customer sees.
  - `status` (string) A namekey.
  - `created` (integer) Unix timestamp.
  - `total` (number) Tax included, in the order currency.
  - `currency_id` (integer) That currency.
- `fields` (object[]) The definitions of your own customer fields.
- `custom_fields` (object) Their values, keyed by namekey. The keys depend on the shop; `fields` says what they are.
- `custom_field_files` (object) For a field holding a file, the file behind the value. Keyed the same way.
    - `text` (string) Several lines, for an invoice or a label.
    - `one_line` (string) One line, for a list.

### Errors

- `not_found` (404) No such customer.
- `already_registered` (400) They already have an account.
- `missing_credentials` (400) A username and a password are both required.
- `invalid_email` (400) The address on the guest record is not usable as an account email.
- `email_taken` (400) Another account has that email address.
- `username_taken` (400) Another account has that login.
- `account_save_failed` (400) The CMS refused to create the account.

---

## Create a product

`POST /products` — public, no token, since 6.6.0

Creates a product and answers with its id. Only the name is required; everything else can be set
now or later with `PUT /products/{id}`.

Prices, images and categories are separate calls, so a new product is not sellable until it has at
least one price.

### Body

Any of the fields a product carries: `name` (required), `code`, `description`, `published`, `msrp`,
`gtin`, `condition`, `weight`, `weight_unit`, `width`, `height`, `length`, `dimension_unit`,
`min_per_order`, `max_per_order`, `sale_start`, `sale_end`, `page_title`, `meta_description`,
`keywords`, `canonical`, `url`, `alias`, `tax_id`, `manufacturer_id`, `contact`, `warehouse_id`,
plus `access` and `custom_fields`.

They mean what they mean on `GET /products/{id}`. A field the shop's own schema does not have is
ignored rather than refused, which is what lets one client talk to shops of different vintages.

### Response

- `id` (string) 
- `name` (string) 
- `code` (string) The SKU. Unique within the shop.
- `description` (string) The long description, as HTML.
- `description_type` (string) Which editor the description was written with.
- `published` (boolean) 
- `quantity` (integer) `-1` when this product does not track stock, which is not the same as `0`.
- `msrp` (integer) The manufacturer's suggested price, shown struck through when the shop is configured to.
- `gtin` (string) The barcode: EAN, UPC or ISBN. This is what `GET /products/lookup` matches on.
- `condition` (string) New, used, refurbished. Used by the feeds rather than by the shop itself.
- `weight` (integer) Shipping weight, in `weight_unit`.
- `weight_unit` (string) `kg`, `g`, `lb` or `oz`.
- `width` (integer) In `dimension_unit`.
- `height` (integer) In `dimension_unit`.
- `length` (integer) In `dimension_unit`.
- `dimension_unit` (string) `m`, `cm`, `mm`, `ft` or `in`.
- `min_per_order` (integer) The smallest quantity a customer may order, `0` for no minimum.
- `max_per_order` (integer) The largest, `0` for no maximum.
- `sale_start` (integer|null) Unix timestamp before which the product is not on sale.
- `sale_end` (integer|null) Unix timestamp after which it is no longer sold.
- `page_title` (string) SEO title, empty to use the name.
- `meta_description` (string) SEO description.
- `keywords` (string) SEO keywords.
- `canonical` (string) A canonical URL, when this page should point at another.
- `url` (string) The address of the product page on the shop.
- `alias` (string) The slug used in that address.
- `access` (object) Who may see the product: `mode` (`all`, `none` or `groups`) and `groups`, which are user **group** ids and not Joomla view levels. The two id spaces overlap and disagree, so a value that looks plausible can grant the wrong audience.
  - `mode` (string) `all`, `none`, or `groups` when it is restricted to some.
  - `groups` (integer[]) User group ids, meaningful only when the mode is `groups`.
- `contact` (boolean) Whether this product is enquired about rather than bought.
- `warehouse_id` (integer) The warehouse holding the stock, `0` when the shop has none.
- `type` (string) `main` for a product, `variant` for one of its variants.
- `parent_id` (integer) The parent product when this is a variant, `0` otherwise.
- `value_ids` (integer[]) For a variant, the characteristic values it stands for.
- `manufacturer_id` (integer) The brand, `0` when unset.
- `manufacturer_name` (string) Its name, saving a second call.
- `tax_id` (integer) The tax category, `0` when the product is untaxed.
- `tax_name` (string) Its name.
- `tax_rate` (number) The rate as a fraction, so `0.2` is twenty percent.
- `prices` (object[]) Every price row, including the restricted ones. A product with none is not sellable.
- `images` (object[]) In the order the editor shows them; the first is the main image.
- `files` (object[]) Downloadable files, in the same shape as the images.
- `categories` (object[]) The categories the product is in.
  - `id` (integer) 
  - `name` (string) 
- `bundle` (object[]) The products this one is made of, when it is a bundle.
- `options` (object[]) Products offered as options alongside this one.
- `related` (object[]) Products shown as related.
- `tags` (integer[]) CMS tag ids.
- `characteristics` (object[]) The characteristics this product varies on. Empty when it has no variants.
- `variants` (object[]) Every variant, with its own code, stock, price and images. Empty for a product that does not vary.
- `fields` (object[]) The definitions of the custom fields that apply to this product, so a client can build a form for them.
  - `namekey` (string) The key used in `custom_fields`.
  - `type` (string) `text`, `radio`, `singledropdown`, `file`, and the rest of HikaShop's field types.
  - `raw_type` (string) HikaShop's own name for the type, before it is mapped to something a client can render.
  - `label` (string) Translated into the operator's language.
  - `default` (string) The value used when none is given.
  - `required` (boolean) Whether the shop refuses to save the product without it.
  - `options` (object[]) The choices, for a field that has them. Empty for a free text one.
  - `multiple` (boolean) Whether more than one choice may be selected.
  - `translatable` (boolean) Whether its value can be translated, which is what the translation routes offer.
  - `upload_dir` (string) For a file field, where its uploads are kept.
  - `allowed_extensions` (string) For a file field, the extensions it accepts, comma separated. Empty means the shop default.
  - `date_format` (string) For a date field, the format it is stored in.
- `custom_fields` (object) Their values, keyed by namekey. The keys depend on the shop; `fields` says what they are.
- `custom_field_files` (object) For custom fields holding a file, the file behind each value. Keyed the same way.
  - `id` (string) 
  - `value` (string) Tax excluded, as stored.
  - `currency_id` (string) 
  - `min_quantity` (string) From how many items this row applies, which is how quantity breaks are expressed.
  - `access` (object) Who this price is for, in the same shape as the product access.
    - `mode` (string) As above.
    - `groups` (integer[]) As above.
  - `users` (integer[]) Named customers, empty for everyone.
  - `zone_ids` (integer[]) Zones this price applies in, empty for everywhere.
  - `start_date` (integer|null) Unix timestamp.
  - `end_date` (integer|null) Unix timestamp.
  - `id` (string) 
  - `name` (string) 
  - `path` (string) Relative to the upload folder.
  - `url` (string) Absolute, ready to display.
  - `ordering` (string) 
  - `description` (string) The alt text.
  - `access` (object) Who may see it.
    - `mode` (string) As above.
    - `groups` (integer[]) As above.
  - `free_download` (string) Files only; meaningless on an image.
  - `id` (string) 
  - `name` (string) 
  - `path` (string) Relative to the upload folder.
  - `url` (string) Absolute.
  - `ordering` (string) 
  - `description` (string) 
  - `access` (object) Who may download it.
  - `free_download` (string) Whether it can be downloaded without buying the product.
  - `id` (string) 
  - `name` (string) 
  - `code` (string) 
  - `quantity` (string) How many of it the bundle contains.
  - `id` (string) 
  - `name` (string) 
  - `code` (string) 
  - `quantity` (string) 
  - `id` (string) 
  - `name` (string) 
  - `code` (string) 
  - `quantity` (string) 
  - `id` (integer) The characteristic, such as Size.
  - `name` (string) Its name.
  - `values` (object[]) The values of it this product uses, such as S, M and L.
    - `id` (integer) The value id, which is what a variant refers to.
    - `value` (string) Its name, such as `M`.
  - `id` (integer) The variant is a product in its own right, and this is its id.
  - `code` (string) Its own SKU.
  - `quantity` (integer) Its own stock. This is the figure to change, not the parent's.
  - `published` (boolean) 
  - `price` (number|null) `null` when the variant has no price of its own and the parent's applies.
  - `values` (object[]) Which characteristic values this variant stands for, one per characteristic.
    - `option_id` (integer) The characteristic.
    - `option_name` (string) Its name, so the variant can be labelled without a second call.
    - `value_id` (integer) The value.
    - `value` (string) Its name.
  - `images` (object[]) The variant's own images, in the same shape as the product's.
    - `id` (integer) 
    - `name` (string) 
    - `path` (string) Relative to the upload folder.
    - `url` (string) Absolute.
    - `ordering` (integer) 

### Errors

- `invalid_fields` (400) One of your own fields was rejected by its own rules.
- `save_failed` (500) The shop refused to save the product.

---

## Update a product

`PUT /products/{id}` — scope `write`, since 6.6.0

Changes the fields you send and leaves the rest alone, which is the opposite of the price and
category calls: this one merges.

Sending nothing to change is refused rather than treated as a success, so a client cannot believe
it saved something it did not.

### Path

- `id` (integer, required) The product or variant.

### Body

The same fields as `POST /products`, all of them optional here. A field you do not send keeps its
value; `access` and `custom_fields` are accepted too.

### Response

- `id` (string) 
- `name` (string) 
- `code` (string) The SKU. Unique within the shop.
- `description` (string) The long description, as HTML.
- `description_type` (string) Which editor the description was written with.
- `published` (boolean) 
- `quantity` (integer) `-1` when this product does not track stock, which is not the same as `0`.
- `msrp` (integer) The manufacturer's suggested price, shown struck through when the shop is configured to.
- `gtin` (string) The barcode: EAN, UPC or ISBN. This is what `GET /products/lookup` matches on.
- `condition` (string) New, used, refurbished. Used by the feeds rather than by the shop itself.
- `weight` (number) Shipping weight, in `weight_unit`.
- `weight_unit` (string) `kg`, `g`, `lb` or `oz`.
- `width` (integer) In `dimension_unit`.
- `height` (integer) In `dimension_unit`.
- `length` (integer) In `dimension_unit`.
- `dimension_unit` (string) `m`, `cm`, `mm`, `ft` or `in`.
- `min_per_order` (integer) The smallest quantity a customer may order, `0` for no minimum.
- `max_per_order` (integer) The largest, `0` for no maximum.
- `sale_start` (integer|null) Unix timestamp before which the product is not on sale.
- `sale_end` (integer|null) Unix timestamp after which it is no longer sold.
- `page_title` (string) SEO title, empty to use the name.
- `meta_description` (string) SEO description.
- `keywords` (string) SEO keywords.
- `canonical` (string) A canonical URL, when this page should point at another.
- `url` (string) The address of the product page on the shop.
- `alias` (string) The slug used in that address.
- `access` (object) Who may see the product: `mode` (`all`, `none` or `groups`) and `groups`, which are user **group** ids and not Joomla view levels. The two id spaces overlap and disagree, so a value that looks plausible can grant the wrong audience.
  - `mode` (string) `all`, `none`, or `groups` when it is restricted to some.
  - `groups` (integer[]) User group ids, meaningful only when the mode is `groups`.
- `contact` (boolean) Whether this product is enquired about rather than bought.
- `warehouse_id` (integer) The warehouse holding the stock, `0` when the shop has none.
- `type` (string) `main` for a product, `variant` for one of its variants.
- `parent_id` (integer) The parent product when this is a variant, `0` otherwise.
- `value_ids` (integer[]) For a variant, the characteristic values it stands for.
- `manufacturer_id` (integer) The brand, `0` when unset.
- `manufacturer_name` (string) Its name, saving a second call.
- `tax_id` (integer) The tax category, `0` when the product is untaxed.
- `tax_name` (string) Its name.
- `tax_rate` (number) The rate as a fraction, so `0.2` is twenty percent.
- `prices` (object[]) Every price row, including the restricted ones. A product with none is not sellable.
  - `id` (integer) 
  - `value` (number) Tax excluded, as stored.
  - `currency_id` (integer) 
  - `min_quantity` (integer) From how many items this row applies, which is how quantity breaks are expressed.
  - `access` (object) Who this price is for, in the same shape as the product access.
    - `mode` (string) As above.
    - `groups` (integer[]) As above.
  - `users` (integer[]) Named customers, empty for everyone.
  - `zone_ids` (integer[]) Zones this price applies in, empty for everywhere.
  - `start_date` (integer|null) Unix timestamp.
  - `end_date` (integer|null) Unix timestamp.
- `images` (object[]) In the order the editor shows them; the first is the main image.
  - `id` (integer) 
  - `name` (string) 
  - `path` (string) Relative to the upload folder.
  - `url` (string) Absolute, ready to display.
  - `ordering` (integer) 
  - `description` (string) The alt text.
  - `access` (object) Who may see it.
    - `mode` (string) As above.
    - `groups` (integer[]) As above.
  - `free_download` (boolean) Files only; meaningless on an image.
- `files` (object[]) Downloadable files, in the same shape as the images.
- `categories` (object[]) The categories the product is in.
  - `id` (integer) 
  - `name` (string) 
- `bundle` (object[]) The products this one is made of, when it is a bundle.
- `options` (object[]) Products offered as options alongside this one.
- `related` (object[]) Products shown as related.
- `tags` (integer[]) CMS tag ids.
- `characteristics` (object[]) The characteristics this product varies on. Empty when it has no variants.
- `variants` (object[]) Every variant, with its own code, stock, price and images. Empty for a product that does not vary.
- `fields` (object[]) The definitions of the custom fields that apply to this product, so a client can build a form for them.
  - `namekey` (string) The key used in `custom_fields`.
  - `type` (string) `text`, `radio`, `singledropdown`, `file`, and the rest of HikaShop's field types.
  - `raw_type` (string) HikaShop's own name for the type, before it is mapped to something a client can render.
  - `label` (string) Translated into the operator's language.
  - `default` (string) The value used when none is given.
  - `required` (boolean) Whether the shop refuses to save the product without it.
  - `options` (object[]) The choices, for a field that has them. Empty for a free text one.
  - `multiple` (boolean) Whether more than one choice may be selected.
  - `translatable` (boolean) Whether its value can be translated, which is what the translation routes offer.
  - `upload_dir` (string) For a file field, where its uploads are kept.
  - `allowed_extensions` (string) For a file field, the extensions it accepts, comma separated. Empty means the shop default.
  - `date_format` (string) For a date field, the format it is stored in.
- `custom_fields` (object) Their values, keyed by namekey. The keys depend on the shop; `fields` says what they are.
- `custom_field_files` (object) For custom fields holding a file, the file behind each value. Keyed the same way.
  - `id` (string) 
  - `name` (string) 
  - `path` (string) Relative to the upload folder.
  - `url` (string) Absolute.
  - `ordering` (string) 
  - `description` (string) 
  - `access` (object) Who may download it.
  - `free_download` (string) Whether it can be downloaded without buying the product.
  - `id` (string) 
  - `name` (string) 
  - `code` (string) 
  - `quantity` (string) How many of it the bundle contains.
  - `id` (string) 
  - `name` (string) 
  - `code` (string) 
  - `quantity` (string) 
  - `id` (string) 
  - `name` (string) 
  - `code` (string) 
  - `quantity` (string) 
  - `id` (integer) The characteristic, such as Size.
  - `name` (string) Its name.
  - `values` (object[]) The values of it this product uses, such as S, M and L.
    - `id` (integer) The value id, which is what a variant refers to.
    - `value` (string) Its name, such as `M`.
  - `id` (integer) The variant is a product in its own right, and this is its id.
  - `code` (string) Its own SKU.
  - `quantity` (integer) Its own stock. This is the figure to change, not the parent's.
  - `published` (boolean) 
  - `price` (number|null) `null` when the variant has no price of its own and the parent's applies.
  - `values` (object[]) Which characteristic values this variant stands for, one per characteristic.
    - `option_id` (integer) The characteristic.
    - `option_name` (string) Its name, so the variant can be labelled without a second call.
    - `value_id` (integer) The value.
    - `value` (string) Its name.
  - `images` (object[]) The variant's own images, in the same shape as the product's.
    - `id` (integer) 
    - `name` (string) 
    - `path` (string) Relative to the upload folder.
    - `url` (string) Absolute.
    - `ordering` (integer) 

### Errors

- `not_found` (404) No such product, or the operator may not change it.
- `invalid_fields` (400) One of your own fields was rejected by its own rules.
- `nothing` (400) The body held no field this shop knows, so nothing would have been written.

---

## Delete a product

`DELETE /products/{id}` — scope `write`, since 6.6.0

Deletes a product and its variants.

Orders that already contain it are untouched: an order line records what was sold at the time, and
it does not stop meaning something because the catalogue changed.

### Path

- `id` (integer, required) The product.

### Response

- `id` (integer) The product that was deleted.
- `deleted` (boolean) True when the row is gone.

### Errors

- `not_found` (404) No such product, or the operator may not delete it.
- `delete_failed` (400) The shop refused to delete it.

---

## Reconcile the variant set

`PUT /products/{id}/variants` — scope `write`, since 6.6.0

Takes the variants a product should have and makes the shop agree: it creates the ones that are
missing, updates the ones that exist and removes the ones you left out.

A variant is identified by the characteristic values it stands for rather than by an id, because
that is what makes it that variant. Send the whole set.

### Path

- `id` (integer, required) The parent product.

### Body

- `variants` (object[], required) The complete set. Each needs the characteristic `values` it stands for, and may carry `code`, `quantity`, `published` and `price`.

### Response

- `characteristics` (object[]) The characteristics the product now varies on, as they stand after the change.
- `variants` (object[]) The variants as they now stand, in the same shape as on the product.

### Errors

- `not_found` (404) No such product, or the operator may not change it.

---

## Edit one variant

`PUT /products/{id}/variants/{vid}` — scope `write`, since 6.6.0

Changes one variant without touching the others, which is what a stock correction or a price change
on a single size needs.

### Path

- `id` (integer, required) The parent product.
- `vid` (integer, required) The variant.

### Body

Any of `code`, `quantity`, `published` and `price`, and your own product fields. What you do not send is left alone.

### Response

- `id` (string) 
- `name` (string) 
- `code` (string) The SKU. Unique within the shop.
- `description` (string) The long description, as HTML.
- `description_type` (string) Which editor the description was written with.
- `published` (boolean) 
- `quantity` (integer) `-1` when this product does not track stock, which is not the same as `0`.
- `msrp` (integer) The manufacturer's suggested price, shown struck through when the shop is configured to.
- `gtin` (string) The barcode: EAN, UPC or ISBN. This is what `GET /products/lookup` matches on.
- `condition` (string) New, used, refurbished. Used by the feeds rather than by the shop itself.
- `weight` (integer) Shipping weight, in `weight_unit`.
- `weight_unit` (string) `kg`, `g`, `lb` or `oz`.
- `width` (integer) In `dimension_unit`.
- `height` (integer) In `dimension_unit`.
- `length` (integer) In `dimension_unit`.
- `dimension_unit` (string) `m`, `cm`, `mm`, `ft` or `in`.
- `min_per_order` (integer) The smallest quantity a customer may order, `0` for no minimum.
- `max_per_order` (integer) The largest, `0` for no maximum.
- `sale_start` (integer|null) Unix timestamp before which the product is not on sale.
- `sale_end` (integer|null) Unix timestamp after which it is no longer sold.
- `page_title` (string) SEO title, empty to use the name.
- `meta_description` (string) SEO description.
- `keywords` (string) SEO keywords.
- `canonical` (string) A canonical URL, when this page should point at another.
- `url` (string) The address of the product page on the shop.
- `alias` (string) The slug used in that address.
- `access` (object) Who may see the product: `mode` (`all`, `none` or `groups`) and `groups`, which are user **group** ids and not Joomla view levels. The two id spaces overlap and disagree, so a value that looks plausible can grant the wrong audience.
  - `mode` (string) `all`, `none`, or `groups` when it is restricted to some.
  - `groups` (integer[]) User group ids, meaningful only when the mode is `groups`.
- `contact` (boolean) Whether this product is enquired about rather than bought.
- `warehouse_id` (integer) The warehouse holding the stock, `0` when the shop has none.
- `type` (string) `main` for a product, `variant` for one of its variants.
- `parent_id` (integer) The parent product when this is a variant, `0` otherwise.
- `value_ids` (integer[]) For a variant, the characteristic values it stands for.
- `manufacturer_id` (integer) The brand, `0` when unset.
- `manufacturer_name` (string) Its name, saving a second call.
- `tax_id` (integer) The tax category, `0` when the product is untaxed.
- `tax_name` (string) Its name.
- `tax_rate` (number) The rate as a fraction, so `0.2` is twenty percent.
- `prices` (object[]) Every price row, including the restricted ones. A product with none is not sellable.
- `images` (object[]) In the order the editor shows them; the first is the main image.
  - `id` (integer) 
  - `name` (string) 
  - `path` (string) Relative to the upload folder.
  - `url` (string) Absolute, ready to display.
  - `ordering` (integer) 
  - `description` (string) The alt text.
  - `access` (object) Who may see it.
    - `mode` (string) As above.
    - `groups` (integer[]) As above.
  - `free_download` (boolean) Files only; meaningless on an image.
- `files` (object[]) Downloadable files, in the same shape as the images.
- `categories` (object[]) The categories the product is in.
- `bundle` (object[]) The products this one is made of, when it is a bundle.
- `options` (object[]) Products offered as options alongside this one.
- `related` (object[]) Products shown as related.
- `tags` (integer[]) CMS tag ids.
- `characteristics` (object[]) The characteristics this product varies on. Empty when it has no variants.
- `variants` (object[]) Every variant, with its own code, stock, price and images. Empty for a product that does not vary.
- `fields` (object[]) The definitions of the custom fields that apply to this product, so a client can build a form for them.
  - `namekey` (string) The key used in `custom_fields`.
  - `type` (string) `text`, `radio`, `singledropdown`, `file`, and the rest of HikaShop's field types.
  - `raw_type` (string) HikaShop's own name for the type, before it is mapped to something a client can render.
  - `label` (string) Translated into the operator's language.
  - `default` (string) The value used when none is given.
  - `required` (boolean) Whether the shop refuses to save the product without it.
  - `options` (object[]) The choices, for a field that has them. Empty for a free text one.
  - `multiple` (boolean) Whether more than one choice may be selected.
  - `translatable` (boolean) Whether its value can be translated, which is what the translation routes offer.
  - `upload_dir` (string) For a file field, where its uploads are kept.
  - `allowed_extensions` (string) For a file field, the extensions it accepts, comma separated. Empty means the shop default.
  - `date_format` (string) For a date field, the format it is stored in.
- `custom_fields` (object) Their values, keyed by namekey. The keys depend on the shop; `fields` says what they are.
- `custom_field_files` (object) For custom fields holding a file, the file behind each value. Keyed the same way.
  - `id` (string) 
  - `value` (string) Tax excluded, as stored.
  - `currency_id` (string) 
  - `min_quantity` (string) From how many items this row applies, which is how quantity breaks are expressed.
  - `access` (object) Who this price is for, in the same shape as the product access.
    - `mode` (string) As above.
    - `groups` (integer[]) As above.
  - `users` (integer[]) Named customers, empty for everyone.
  - `zone_ids` (integer[]) Zones this price applies in, empty for everywhere.
  - `start_date` (integer|null) Unix timestamp.
  - `end_date` (integer|null) Unix timestamp.
  - `id` (string) 
  - `name` (string) 
  - `path` (string) Relative to the upload folder.
  - `url` (string) Absolute.
  - `ordering` (string) 
  - `description` (string) 
  - `access` (object) Who may download it.
  - `free_download` (string) Whether it can be downloaded without buying the product.
  - `id` (string) 
  - `name` (string) 
  - `id` (string) 
  - `name` (string) 
  - `code` (string) 
  - `quantity` (string) How many of it the bundle contains.
  - `id` (string) 
  - `name` (string) 
  - `code` (string) 
  - `quantity` (string) 
  - `id` (string) 
  - `name` (string) 
  - `code` (string) 
  - `quantity` (string) 
  - `id` (integer) The characteristic, such as Size.
  - `name` (string) Its name.
  - `values` (object[]) The values of it this product uses, such as S, M and L.
    - `id` (integer) The value id, which is what a variant refers to.
    - `value` (string) Its name, such as `M`.
  - `id` (integer) The variant is a product in its own right, and this is its id.
  - `code` (string) Its own SKU.
  - `quantity` (integer) Its own stock. This is the figure to change, not the parent's.
  - `published` (boolean) 
  - `price` (number|null) `null` when the variant has no price of its own and the parent's applies.
  - `values` (object[]) Which characteristic values this variant stands for, one per characteristic.
    - `option_id` (integer) The characteristic.
    - `option_name` (string) Its name, so the variant can be labelled without a second call.
    - `value_id` (integer) The value.
    - `value` (string) Its name.
  - `images` (object[]) The variant's own images, in the same shape as the product's.
    - `id` (integer) 
    - `name` (string) 
    - `path` (string) Relative to the upload folder.
    - `url` (string) Absolute.
    - `ordering` (integer) 

### Errors

- `not_found` (404) No such variant, or the operator may not change it.
- `invalid_fields` (400) One of your own fields was rejected by its own rules.

---

## Apply a coupon to an order

`POST /orders/{id}/coupon` — scope `write`, since 6.6.0

Applies a coupon by its code and re-totals the order.

The shop validates it again here against this order: its dates, its quota, its minimum, the
products in the cart and the customer. A coupon that appears in `GET /coupons` can still be refused
for this particular order, which is the point of validating rather than trusting the list.

### Path

- `id` (integer, required) The order.

### Body

- `code` (string, required) The coupon code, as the customer would type it.

### Response

- `id` (integer) The order.
- `fees` (object) The discount, shipping and payment amounts of the order.
- `totals` (object) The order totalled, so a client need not compute it and disagree with the shop.
  - `discount` (object) Its `amount`, its `tax`, the `tax_namekeys` behind that tax, and the coupon `code` when one was used.
    - `amount` (number) A positive figure, already subtracted from the total.
    - `tax` (number) The tax on it.
    - `tax_namekeys` (string[]) Which tax rates that came from.
    - `code` (string) The coupon code, empty for a discount applied by hand.
  - `shipping` (object) The shipping charge and what carried it.
    - `amount` (number) Tax excluded.
    - `tax` (number) The tax on it.
    - `tax_namekeys` (string[]) Which tax rates that came from.
    - `method` (string) The plugin that handled it.
    - `method_name` (string) As the merchant named it.
  - `payment` (object) The payment fee and what took it.
    - `amount` (number) Tax excluded.
    - `tax` (number) The tax on it.
    - `tax_namekeys` (string[]) Which tax rates that came from.
    - `method` (string) The plugin that took it.
    - `method_name` (string) As the merchant named it.
  - `total` (number) What the customer owes, tax included.
  - `discount` (number) The discount applied, as a positive figure already subtracted.
  - `shipping` (number) The shipping charged.
  - `payment` (number) The payment fee charged.
  - `tax` (number) The tax within the total, not on top of it.

### Errors

- `not_found` (404) No such order, or the operator may not change it.
- `invalid_coupon` (400) No such code, or it does not apply to this order.
- `save_failed` (500) The order could not be saved.

---

## Remove the discount from an order

`DELETE /orders/{id}/coupon` — scope `write`, since 6.6.0

Takes the coupon or the hand-applied discount off the order and re-totals it. There is nothing to
send.

### Path

- `id` (integer, required) The order.

### Response

- `id` (integer) The order.
- `fees` (object) The discount, shipping and payment amounts of the order.
- `totals` (object) The order totalled, so a client need not compute it and disagree with the shop.
  - `discount` (object) Its `amount`, its `tax`, the `tax_namekeys` behind that tax, and the coupon `code` when one was used.
    - `amount` (number) A positive figure, already subtracted from the total.
    - `tax` (number) The tax on it.
    - `tax_namekeys` (string[]) Which tax rates that came from.
    - `code` (string) The coupon code, empty for a discount applied by hand.
  - `shipping` (object) The shipping charge and what carried it.
    - `amount` (number) Tax excluded.
    - `tax` (number) The tax on it.
    - `tax_namekeys` (string[]) Which tax rates that came from.
    - `method` (string) The plugin that handled it.
    - `method_name` (string) As the merchant named it.
  - `payment` (object) The payment fee and what took it.
    - `amount` (number) Tax excluded.
    - `tax` (number) The tax on it.
    - `tax_namekeys` (string[]) Which tax rates that came from.
    - `method` (string) The plugin that took it.
    - `method_name` (string) As the merchant named it.
  - `total` (number) What the customer owes, tax included.
  - `discount` (number) The discount applied, as a positive figure already subtracted.
  - `shipping` (number) The shipping charged.
  - `payment` (number) The payment fee charged.
  - `tax` (number) The tax within the total, not on top of it.

### Errors

- `not_found` (404) No such order, or the operator may not change it.
- `save_failed` (500) The order could not be saved.

---

## Add a line to an order

`POST /orders/{id}/products` — scope `write`, since 6.6.0

Adds a product to an existing order and re-totals it.

Send a `price` only to override what the shop would charge; leave it out and the order's own
pricing applies, which is what `GET /orders/{id}/products/precompute` shows you beforehand.

### Path

- `id` (integer, required) The order.

### Body

- `product_id` (integer, required) The product or variant to add.
- `quantity` (integer) How many. Defaults to `1`.
- `price` (number) Unit price, tax excluded, to override the shop. Omit to let the shop price it.
- `tax_namekeys` (string[]) Which tax rates to apply, when overriding the price. Omit to let the shop decide.

### Response

- `id` (integer) The order.
- `items` (object[]) The lines as they now stand, in the same shape as on the order.
- `totals` (object) The order totalled, so a client need not compute it and disagree with the shop.
  - `id` (integer) The line id, which is what the line routes take. It is not the product id.
  - `name` (string) The product as it was named when ordered.
  - `code` (string) Its SKU at the time.
  - `quantity` (integer) 
  - `price` (number) Unit price, tax excluded, as agreed at the time.
  - `tax` (number) Tax on the line.
  - `editable` (boolean) False once the line can no longer be changed.
  - `total` (number) What the customer owes, tax included.
  - `discount` (number) The discount applied, as a positive figure already subtracted.
  - `shipping` (number) The shipping charged.
  - `payment` (number) The payment fee charged.
  - `tax` (number) The tax within the total, not on top of it.

### Errors

- `not_found` (404) No such order or product, or the operator may not change the order.
- `save_failed` (500) The order could not be saved.

---

## Change a line's quantity

`PUT /orders/{id}/products/{lineId}` — scope `write`, since 6.6.0

Changes how many of one line the order holds, and re-totals it. A quantity of `0` removes the line.

The id in the path is the **line** id from `items`, not the product id: the same product can appear
on an order more than once.

### Path

- `id` (integer, required) The order.
- `lineId` (integer, required) The line, from `items[].id` on the order.

### Body

- `quantity` (integer, required) The new quantity. `0` removes the line.

### Response

- `id` (integer) The order.
- `items` (object[]) The lines as they now stand.
- `totals` (object) The order totalled, so a client need not compute it and disagree with the shop.
  - `id` (integer) The line id, which is what the line routes take. It is not the product id.
  - `name` (string) The product as it was named when ordered.
  - `code` (string) Its SKU at the time.
  - `quantity` (integer) 
  - `price` (number) Unit price, tax excluded, as agreed at the time.
  - `tax` (number) Tax on the line.
  - `editable` (boolean) False once the line can no longer be changed.
  - `total` (number) What the customer owes, tax included.
  - `discount` (number) The discount applied, as a positive figure already subtracted.
  - `shipping` (number) The shipping charged.
  - `payment` (number) The payment fee charged.
  - `tax` (number) The tax within the total, not on top of it.

### Errors

- `not_found` (404) No such order or line, or the operator may not change it.
- `save_failed` (500) The order could not be saved.

---

## Run a mass action

`POST /massactions/{id}` — scope `write`, since 6.6.0

Runs one of the merchant's own bulk operations over a selection.

It runs the same code the backend runs, so whatever the action does there it does here, including
whatever a third-party plugin added to it. Give it the ids to work on.

### Path

- `id` (integer, required) The mass action, from `GET /massactions`.

### Body

- `ids` (integer[], required) The records to run it over, from the listing the action belongs to.

### Response

- `ok` (boolean) Whether the action reported success.
- `count` (integer) How many records it worked on.
- `report` (string) Whatever the action had to say, ready to show. Its wording is the action's own.

### Errors

- `invalid_request` (400) No ids were sent, or the action is not one this shop has.
- `forbidden` (403) The operator may not work on that kind of record.

---

## Create an order by hand

`POST /orders` — scope `write`, since 6.6.0

Creates an empty order for a customer, to be filled in with lines, fees and an address.

Give it either an existing `user_id` or a `guest` with at least an email address, which is how an
order gets taken over the telephone from somebody who has never bought before.

### Body

- `user_id` (integer) An existing customer. Give this or `guest`.
- `guest` (object) A new guest customer, needing at least `email` and usually a name.

### Response

- `id` (integer) The order that was created, to add lines to.

### Errors

- `no_customer` (400) Neither a user_id nor a usable guest was given.
- `save_failed` (500) The order could not be created.

---

## Save an order's custom fields

`PUT /orders/{id}/fields` — scope `write`, since 6.6.0

Writes the merchant's own order fields. Only the fields you send are touched.

### Path

- `id` (integer, required) The order.

### Body

- `fields` (object, required) Keyed by field namekey.

### Response

- `id` (integer) The order.
- `custom_fields` (object) The values as they now stand. The keys depend on the shop.
- `custom_field_files` (object) For a field holding a file, the file behind the value. Keyed the same way.

### Errors

- `not_found` (404) No such order, or the operator may not change it.
- `invalid_fields` (400) A field was rejected by its own rules.
- `save_failed` (500) The order could not be saved.

---

## Save an address of an order

`PUT /orders/{id}/address/{type}` — scope `write`, since 6.6.0

Writes the billing or shipping address of an order, using the fields `GET` gave you.

The address on an order is its own copy, so changing it here does not change the customer's address
book, and vice versa. That is deliberate: an invoice should not change because somebody moved.

### Path

- `id` (integer, required) The order.
- `type` (string, required) `billing` or `shipping`.

### Body

- `fields` (object, required) Keyed by address field namekey, the same keys `GET` returned in `values`.

### Response

- `type` (string) Which address was written.
- `address_id` (integer) The address row, created if there was none.
- `values` (object) The values as stored. Keyed by whatever address fields this shop has.
- `country_name` (string) The country spelled out.
- `state_name` (string) The state spelled out, empty where the country has none.
- `summary` (object) The address ready to show, without re-reading the order.
  - `name` (string) 
  - `company` (string) 
  - `formatted` (object) Laid out the way this shop lays addresses out, which follows its address format setting.
  - `street` (string) 
  - `city` (string) 
  - `post_code` (string) 
    - `text` (string) Several lines, for an invoice or a label.
    - `one_line` (string) One line, for a list.

### Errors

- `not_found` (404) No such order, or the operator may not change it.
- `invalid_fields` (400) A field was rejected by its own rules.
- `invalid_address` (400) The address is not one the shop will accept, usually a missing required field.

---

## Attach an image or a file

`POST /products/{id}/images` — scope `write`, since 6.6.0

Adds an image or a downloadable file to a product, either by sending the bytes or by pointing at
something already in the upload folder.

Send `data` as base64 to upload, or `path` to attach a file that `GET /media/browse` already showed
you. The extension is checked against what the shop allows, so a refusal is the shop's policy rather
than a fault.

The same route serves `/images` and `/files`; which one you call decides where it goes.

### Path

- `id` (integer, required) The product.

### Body

- `data` (string) The bytes, base64 encoded. Give this or `path`.
- `path` (string) A file already in the upload folder, relative to it, as `GET /media/browse` returns.
- `name` (string) The file name to store it under. Defaults to the one in the path.
- `description` (string) The alt text for an image, or a note on a file.
- `access` (object) Who may see or download it, in the usual mode and groups shape.

### Response

- `id` (integer) 
- `name` (string) 
- `path` (string) Relative to the upload folder.
- `url` (string) Absolute, ready to display.
- `ordering` (integer) Where it sits among the others; the first image is the one the shop shows.
- `description` (string) The alt text for an image, or a note on a file.
- `access` (object) Who may see or download it.
  - `mode` (string) `all`, `none`, or `groups` when it is restricted to some.
  - `groups` (integer[]) User **group** ids, and not Joomla view levels. The two id spaces overlap and disagree, so a value that looks plausible can grant the wrong audience.
- `free_download` (boolean) Files only: whether it can be downloaded without buying the product.

### Errors

- `not_found` (404) No such product, or the operator may not change it.
- `bad_type` (400) The extension is not one this shop accepts.
- `write_failed` (500) The upload folder refused the file, which is usually a permissions problem.

---

## Edit an image or a file

`PUT /products/{id}/files/{fileId}` — scope `write`, since 6.6.0

Changes the name, the description or the access of something already attached, without re-uploading
the bytes.

### Path

- `id` (integer, required) The product.
- `fileId` (integer, required) The image or file.

### Body

Any of `name`, `description`, `access` and, for a file, `free_download`. What you do not send is left alone.

### Response

- `id` (integer) 
- `name` (string) 
- `path` (string) Relative to the upload folder.
- `url` (string) Absolute, ready to display.
- `ordering` (integer) Where it sits among the others; the first image is the one the shop shows.
- `description` (string) The alt text for an image, or a note on a file.
- `access` (object) Who may see or download it.
  - `mode` (string) `all`, `none`, or `groups` when it is restricted to some.
  - `groups` (integer[]) User **group** ids, and not Joomla view levels. The two id spaces overlap and disagree, so a value that looks plausible can grant the wrong audience.
- `free_download` (boolean) Files only: whether it can be downloaded without buying the product.

### Errors

- `not_found` (404) No such file on that product, or the operator may not change it.

---

## Remove an image or a file

`DELETE /products/{id}/files/{fileId}` — scope `write`, since 6.6.0

Detaches an image or a file from a product.

The bytes stay in the upload folder: another product may be using the same file, and the API will
not delete somebody's media because one product stopped pointing at it.

### Path

- `id` (integer, required) The product.
- `fileId` (integer, required) The image or file.

### Response

- `id` (integer) What was detached.
- `deleted` (boolean) True when the row is gone.

### Errors

- `not_found` (404) No such file on that product, or the operator may not change it.

---

## Reorder images and files

`PUT /products/{id}/media/order` — scope `write`, since 6.6.0

Sets the order of a product's images or files. The first image is the one the shop shows, so this is
how you choose it.

### Path

- `id` (integer, required) The product.

### Body

- `images` (integer[]) File ids in the order you want them.
- `files` (integer[]) The same for downloadable files.

### Response

- `images` (object[]) The images as they now stand, in their new order, each in the same shape as on the product.
- `files` (object[]) The files as they now stand, in the same shape as on the product.
  - `id` (integer) 
  - `name` (string) 
  - `path` (string) Relative to the upload folder.
  - `url` (string) Absolute, ready to display.
  - `ordering` (integer) Where it sits among the others; the first image is the one the shop shows.
  - `description` (string) The alt text for an image, or a note on a file.
  - `access` (object) Who may see or download it.
    - `mode` (string) `all`, `none`, or `groups` when it is restricted to some.
    - `groups` (integer[]) User **group** ids, and not Joomla view levels. The two id spaces overlap and disagree, so a value that looks plausible can grant the wrong audience.
  - `free_download` (boolean) Files only: whether it can be downloaded without buying the product.
  - `id` (integer) 
  - `name` (string) 
  - `path` (string) Relative to the upload folder.
  - `url` (string) Absolute, ready to display.
  - `ordering` (integer) Where it sits among the others; the first image is the one the shop shows.
  - `description` (string) The alt text for an image, or a note on a file.
  - `access` (object) Who may see or download it.
  - `free_download` (boolean) Files only: whether it can be downloaded without buying the product.
    - `mode` (string) `all`, `none`, or `groups` when it is restricted to some.
    - `groups` (integer[]) User **group** ids, and not Joomla view levels. The two id spaces overlap and disagree, so a value that looks plausible can grant the wrong audience.

### Errors

- `not_found` (404) No such product, or the operator may not change it.

---

## The bytes of an image

`GET /media/content` — scope `read`, since 6.6.0

The bytes of one image from the upload folder, so a client can edit it: crop it, rotate it, and send
it back.

This is the one route that does not answer with the envelope. It answers with the file, and with the
cross-origin header that lets a browser read it into a canvas, which the shop's own image URLs do
not carry. Without it, a web client cannot export an edited image at all.

A path that tries to leave the upload folder is refused rather than resolved.

### Query

- `path` (string) The image, relative to the upload folder.

### Response


### Errors

- `not_found` (404) No such file, or a path that tried to leave the upload folder.

---

## Upload the value of a file field

`POST /fields/{table}/{namekey}/file` — scope `write`, since 6.6.0

Uploads the file that is the value of one of your own custom fields. Only the two field types that
hold one accept it: `ajaximage` and `ajaxfile`.

The field is named rather than numbered, and the table says which kind of record it belongs to, so
the same route serves a product field, a category field and a customer field.

### Path

- `table` (string, required) Which kind of record: `product`, `category`, `user`, `order`, `address`.
- `namekey` (string, required) The field, as `fields[].namekey` gives it.

### Body

- `data` (string, required) The bytes, base64 encoded.
- `name` (string) The file name to store it under.

### Response

- `path` (string) Relative to the field's own upload folder. This is the value to store on the record.
- `name` (string) The file name it was stored under.
- `url` (string) Absolute, where the shop serves it from.

### Errors

- `not_found` (404) No such record kind.
- `bad_field` (400) No such field, or it does not hold a file.
- `bad_type` (400) The extension is not one this shop accepts.
- `write_failed` (500) The upload folder refused the file.

---

## The shipping and payment methods an order can move to

`GET /orders/{id}/methods` — scope `read`, since 6.6.0

What this order could ship and be paid by, with what it uses now, so a change can be offered as a
choice rather than as free text.

A method is identified by its plugin and its instance together, written `plugin_id`, which is the
pairing the backend posts back. An order that ships from several warehouses carries one method per
shipment, and `multiple` says so.

### Path

- `id` (integer, required) The order.

### Response

- `shipping` (object) The shipping choice.
  - `current` (string) What the order uses now, as `plugin_id`. `_` when nothing is set, so the current one always matches an option.
  - `multiple` (boolean) True when the order ships in several shipments and carries a method for each.
  - `options` (object[]) What it could move to.
    - `value` (string) The `plugin_id` pairing to send when changing the method.
    - `label` (string) As the merchant named it.
  - `groups` (object[]) For an order that ships in several shipments, the shipments and the method chosen for each. Empty otherwise.
- `payment` (object) The payment choice, in the same shape.
  - `current` (string) What the order uses now.
  - `multiple` (boolean) Always false: an order is paid one way.
  - `options` (object[]) What it could move to.
    - `value` (string) The `plugin_id` pairing to send.
    - `label` (string) As the merchant named it.

### Errors

- `not_found` (404) No such order, or the operator may not see it.

---

## Make an address the default

`PUT /customers/{id}/addresses/{aid}/default` — scope `write`, since 6.6.0

Marks one of a customer's addresses as their default, and answers with the customer as it now
stands.

The default is per kind: an address used for billing becomes the default billing address, and the
one it replaces stops being it. Nothing is sent in the body.

### Path

- `id` (integer, required) The customer.
- `aid` (integer, required) The address, from `addresses[].id`.

### Response

- `id` (integer) The HikaShop customer id, which is what every customer route takes.
- `cms_id` (integer) The Joomla or WordPress user id, `0` for a guest with no account.
- `name` (string) 
- `email` (string) 
- `username` (string) The login, empty for a guest.
- `type` (string) `registered` or `guest`.
- `blocked` (boolean) Whether the CMS account is disabled.
- `can_edit_account` (boolean) Whether this operator may change the login and password of this particular account. False for an account above their own level, which is how a super user is protected from staff.
- `groups_editable` (boolean) Whether this operator may change which groups the customer is in.
- `groups` (object[]) The groups they are in.
  - `id` (integer) 
  - `title` (string) 
- `available_groups` (object[]) Every group, with whether this operator may put the customer into it, so a picker can grey out the rest rather than offering a refusal.
  - `id` (integer) 
  - `title` (string) 
  - `assignable` (boolean) False for a group this operator may not grant.
- `created` (integer) Unix timestamp of the first time the shop saw them.
- `addresses` (object[]) Their addresses, defaults first.
  - `id` (integer) 
  - `types` (string[]) Which of `billing` and `shipping` it is used for.
  - `name` (string) 
  - `company` (string) 
  - `street` (string) 
  - `city` (string) 
  - `post_code` (string) 
  - `telephone` (string) 
  - `default` (boolean) Whether it is the default for one of its types.
  - `formatted` (object) The address laid out the way this shop lays addresses out, which depends on its address format setting.
- `orders` (object[]) Their orders, newest first, enough to list them.
  - `id` (integer) 
  - `number` (string) The number the customer sees.
  - `status` (string) A namekey.
  - `created` (integer) Unix timestamp.
  - `total` (number) Tax included, in the order currency.
  - `currency_id` (integer) That currency.
- `fields` (object[]) The definitions of your own customer fields.
- `custom_fields` (object) Their values, keyed by namekey. The keys depend on the shop; `fields` says what they are.
- `custom_field_files` (object) For a field holding a file, the file behind the value. Keyed the same way.
    - `text` (string) Several lines, for an invoice or a label.
    - `one_line` (string) One line, for a list.

### Errors

- `not_found` (404) No such customer or address, or the operator may not change them.

---

## Create a characteristic or one of its values

`POST /products/characteristics` — scope `write`, since 6.6.0

Creates a characteristic, such as Size, or a value of one, such as XL.

Send a `name` for a characteristic, or a `value` with the `parent_id` of the characteristic it
belongs to. They are the same records at two levels, which is why one route serves both.

### Body

- `name` (string) The name of a new characteristic. Give this or `value`.
- `value` (string) The name of a new value, with `parent_id`.
- `parent_id` (integer) The characteristic a value belongs to.

### Response

- `id` (integer) What was created, which is what a variant refers to.
- `value` (string) Its name.
- `parent_id` (integer) `0` for a characteristic, the characteristic for a value.

### Errors

- `invalid_request` (400) Neither a name nor a value was given.
- `save_failed` (500) The shop refused to save it.

---

## Create a product category

`POST /products/categories` — scope `write`, since 6.6.0

Creates a category under another, or at the top of the product tree when no parent is given.

### Body

- `name` (string, required) Its name.
- `parent_id` (integer) The category to put it under. Omit for the top of the tree.
- `published` (boolean) Defaults to published.
- `description` (string) The long description, as HTML.
- `meta_description` (string) SEO description.
- `access` (object) Who may see it.
- `custom_fields` (object) Your own category fields, keyed by namekey.

### Response

- `id` (integer) The category that was created.
- `name` (string) 
- `parent_id` (integer) Where it sits.
- `published` (boolean) 

### Errors

- `invalid_fields` (400) One of your own fields was rejected by its own rules.
- `save_failed` (500) The shop refused to save it.

---

## Create a manufacturer

`POST /products/manufacturers` — scope `write`, since 6.6.0

Creates a brand. HikaShop keeps manufacturers in the category tree under their own root, so this
takes the same fields as a category and answers the same way.

### Body

- `name` (string, required) The brand name.
- `parent_id` (integer) A manufacturer to nest it under, which most shops do not use.
- `published` (boolean) Defaults to published.
- `description` (string) The long description, as HTML.
- `meta_description` (string) SEO description.
- `access` (object) Who may see it.
- `custom_fields` (object) Your own category fields.

### Response

- `id` (integer) The manufacturer that was created, which is what `manufacturer_id` on a product stores.
- `name` (string) 
- `parent_id` (integer) Its root.
- `published` (boolean) 

### Errors

- `invalid_fields` (400) One of your own fields was rejected by its own rules.
- `save_failed` (500) The shop refused to save it.

---

## Update a category

`PUT /categories/{id}` — scope `write`, since 6.6.0

Changes the fields you send and leaves the rest alone. It serves manufacturers and the other trees
as well, since they are all categories.

### Path

- `id` (integer, required) The category.

### Body

Any of `name`, `parent_id`, `published`, `description`, `meta_description`, `access` and `custom_fields`. What you do not send keeps its value.

### Response

- `id` (string) 
- `fields` (object[]) The definitions of your own category fields.
- `name` (string) 
- `parent_id` (integer) Its parent.
- `type` (string) Which tree it belongs to: `product`, `manufacturer`, `tax`, and so on.
- `description` (string) The long description, as HTML.
- `meta_description` (string) SEO description.
- `published` (boolean) 
- `access` (object) Who may see it.
  - `mode` (string) `all`, `none`, or `groups` when it is restricted to some.
  - `groups` (integer[]) User **group** ids, and not Joomla view levels. The two id spaces overlap and disagree, so a value that looks plausible can grant the wrong audience.
- `image` (string|null) Absolute URL of its image, `null` when it has none.
- `custom_fields` (object) Their values, keyed by namekey. The keys depend on the shop; `fields` says what they are.
- `custom_field_files` (object) For a field holding a file, the file behind the value. Keyed the same way.
  - `namekey` (string) The key its value is stored under.
  - `label` (string) Translated into the operator's language.
  - `type` (string) What to render: `text`, `radio`, `singledropdown`, `file`, and the rest.
  - `raw_type` (string) HikaShop's own name for the type.
  - `required` (boolean) Whether the shop refuses to save without it.
  - `default` (string) The value used when none is given.
  - `options` (object[]) The choices, for a field that has them.
    - `value` (string) What to send back when this choice is picked.
    - `label` (string) What to show.
    - `label_key` (string) The translation key behind the label, when there is one.
  - `multiple` (boolean) Whether more than one may be chosen.
  - `translatable` (boolean) Whether its value can be translated.
  - `upload_dir` (string) For a file field, where its uploads are kept.
  - `allowed_extensions` (string) For a file field, the extensions it accepts. Empty means the shop default.
  - `date_format` (string) For a date field, the format it is stored in.

### Errors

- `not_found` (404) No such category, or the operator may not change it.
- `invalid_fields` (400) One of your own fields was rejected by its own rules.

---

## Delete a category

`DELETE /categories/{id}` — scope `write`, since 6.6.0

Deletes a category. The products in it are not deleted; they simply stop being in it, and a product
left in no category at all disappears from the shop's listings.

### Path

- `id` (integer, required) The category.

### Response

- `id` (integer) The category that was deleted.
- `deleted` (boolean) True when the row is gone.

### Errors

- `not_found` (404) No such category, or the operator may not delete it.

---

## Read one discount or coupon

`GET /discounts/{id}` — scope `read`, since 6.6.0

One reduction with every restriction it carries.

### Path

- `id` (integer, required) The discount or coupon.

### Response

- `id` (integer) 
- `type` (string) `discount` applies by itself, `coupon` waits for its code.
- `code` (string) What the customer types. Empty on a discount.
- `kind` (string) Whether the value is a percentage or a fixed amount.
- `value` (number) The reduction, read according to `kind`.
- `currency_id` (integer) The currency a fixed amount is in.
- `published` (boolean) 
- `start` (integer|null) Unix timestamp before which it does not apply.
- `end` (integer|null) Unix timestamp after which it expires.
- `minimum_order` (number) Order total below which it does not apply, `0` for none.
- `maximum_order` (number) Order total above which it stops applying, `0` for none.
- `quota` (integer) How many times it may be used in total, `0` for no limit.
- `quota_per_user` (integer) How many times one customer may use it, `0` for no limit.
- `used_times` (integer) How many times it already has been.
- `tax_included` (boolean) Whether the value is understood as tax included.
- `tax_id` (integer) The tax category of the reduction itself.
- `shipping_percent` (number) A reduction on the shipping rather than on the goods.
- `minimum_products` (integer) Fewest items in the cart for it to apply.
- `maximum_products` (integer) Most items for it to still apply.
- `product_ids` (integer[]) Restricted to these products. Empty means all of them.
- `exclude_product_ids` (integer[]) Never applies to these.
- `category_ids` (integer[]) Restricted to these categories.
- `category_childs` (boolean) Whether those categories include their sub-categories.
- `exclude_category_ids` (integer[]) Never applies in these categories.
- `exclude_category_childs` (boolean) Whether those exclusions include sub-categories.
- `zone_ids` (integer[]) Restricted to these zones.
- `user_ids` (integer[]) Restricted to these customers.
- `access` (object) Which user groups it is for, in the usual `mode` and `groups` shape.
- `exclude_access` (object) Which user groups it is never for.
- `auto_load` (boolean) For a coupon, whether the shop applies it without the customer typing it.
- `product_only` (boolean) Whether it reduces only the goods and leaves the fees alone.
- `discounted_products` (integer) How many products in the cart it applied to, on a discount that has been used.
  - `mode` (string) `all`, `none`, or `groups` when it is restricted to some.
  - `groups` (integer[]) User **group** ids, and not Joomla view levels. The two id spaces overlap and disagree, so a value that looks plausible can grant the wrong audience.
  - `mode` (string) `all`, `none`, or `groups` when it is restricted to some.
  - `groups` (integer[]) User **group** ids, and not Joomla view levels. The two id spaces overlap and disagree, so a value that looks plausible can grant the wrong audience.

### Errors

- `not_found` (404) No such discount, or the operator may not see it.

---

## Create a discount or a coupon

`POST /discounts` — scope `write`, since 6.6.0

Creates a reduction and answers with it as the shop stored it.

`type` decides which kind it is: a `coupon` needs a `code`, a `discount` applies by itself. Either
way `value` is required, read according to `kind`.

### Body

Any field of a discount. `type` and `value` are required, and a coupon also needs a `code`. The restriction lists take ids, and an empty list means no restriction of that kind.

### Response

- `id` (integer) 
- `type` (string) `discount` applies by itself, `coupon` waits for its code.
- `code` (string) What the customer types. Empty on a discount.
- `kind` (string) Whether the value is a percentage or a fixed amount.
- `value` (number) The reduction, read according to `kind`.
- `currency_id` (integer) The currency a fixed amount is in.
- `published` (boolean) 
- `start` (integer|null) Unix timestamp before which it does not apply.
- `end` (integer|null) Unix timestamp after which it expires.
- `minimum_order` (number) Order total below which it does not apply, `0` for none.
- `maximum_order` (number) Order total above which it stops applying, `0` for none.
- `quota` (integer) How many times it may be used in total, `0` for no limit.
- `quota_per_user` (integer) How many times one customer may use it, `0` for no limit.
- `used_times` (integer) How many times it already has been.
- `tax_included` (boolean) Whether the value is understood as tax included.
- `tax_id` (integer) The tax category of the reduction itself.
- `shipping_percent` (number) A reduction on the shipping rather than on the goods.
- `minimum_products` (integer) Fewest items in the cart for it to apply.
- `maximum_products` (integer) Most items for it to still apply.
- `product_ids` (integer[]) Restricted to these products. Empty means all of them.
- `exclude_product_ids` (integer[]) Never applies to these.
- `category_ids` (integer[]) Restricted to these categories.
- `category_childs` (boolean) Whether those categories include their sub-categories.
- `exclude_category_ids` (integer[]) Never applies in these categories.
- `exclude_category_childs` (boolean) Whether those exclusions include sub-categories.
- `zone_ids` (integer[]) Restricted to these zones.
- `user_ids` (integer[]) Restricted to these customers.
- `access` (object) Which user groups it is for, in the usual `mode` and `groups` shape.
- `exclude_access` (object) Which user groups it is never for.
- `auto_load` (boolean) For a coupon, whether the shop applies it without the customer typing it.
- `product_only` (boolean) Whether it reduces only the goods and leaves the fees alone.
- `discounted_products` (integer) How many products in the cart it applied to, on a discount that has been used.
  - `mode` (string) `all`, `none`, or `groups` when it is restricted to some.
  - `groups` (integer[]) User **group** ids, and not Joomla view levels. The two id spaces overlap and disagree, so a value that looks plausible can grant the wrong audience.
  - `mode` (string) `all`, `none`, or `groups` when it is restricted to some.
  - `groups` (integer[]) User **group** ids, and not Joomla view levels. The two id spaces overlap and disagree, so a value that looks plausible can grant the wrong audience.

### Errors

- `code_required` (400) A coupon needs a code.
- `code_taken` (400) Another coupon already uses that code.
- `value_required` (400) A reduction needs a value.
- `not_found` (404) Not reachable when creating: the same handler serves the update, where it means no such discount.
- `save_failed` (500) The shop refused to save it.

---

## Update a discount or a coupon

`PUT /discounts/{id}` — scope `write`, since 6.6.0

Changes the fields you send and leaves the rest alone, and answers with the reduction as it now
stands.

### Path

- `id` (integer, required) The discount or coupon.

### Body

Any field of a discount. What you do not send keeps its value. A restriction list you do send replaces the one it had.

### Response

- `id` (integer) 
- `type` (string) `discount` applies by itself, `coupon` waits for its code.
- `code` (string) What the customer types. Empty on a discount.
- `kind` (string) Whether the value is a percentage or a fixed amount.
- `value` (number) The reduction, read according to `kind`.
- `currency_id` (integer) The currency a fixed amount is in.
- `published` (boolean) 
- `start` (integer|null) Unix timestamp before which it does not apply.
- `end` (integer|null) Unix timestamp after which it expires.
- `minimum_order` (number) Order total below which it does not apply, `0` for none.
- `maximum_order` (number) Order total above which it stops applying, `0` for none.
- `quota` (integer) How many times it may be used in total, `0` for no limit.
- `quota_per_user` (integer) How many times one customer may use it, `0` for no limit.
- `used_times` (integer) How many times it already has been.
- `tax_included` (boolean) Whether the value is understood as tax included.
- `tax_id` (integer) The tax category of the reduction itself.
- `shipping_percent` (number) A reduction on the shipping rather than on the goods.
- `minimum_products` (integer) Fewest items in the cart for it to apply.
- `maximum_products` (integer) Most items for it to still apply.
- `product_ids` (integer[]) Restricted to these products. Empty means all of them.
- `exclude_product_ids` (integer[]) Never applies to these.
- `category_ids` (integer[]) Restricted to these categories.
- `category_childs` (boolean) Whether those categories include their sub-categories.
- `exclude_category_ids` (integer[]) Never applies in these categories.
- `exclude_category_childs` (boolean) Whether those exclusions include sub-categories.
- `zone_ids` (integer[]) Restricted to these zones.
- `user_ids` (integer[]) Restricted to these customers.
- `access` (object) Which user groups it is for, in the usual `mode` and `groups` shape.
- `exclude_access` (object) Which user groups it is never for.
- `auto_load` (boolean) For a coupon, whether the shop applies it without the customer typing it.
- `product_only` (boolean) Whether it reduces only the goods and leaves the fees alone.
- `discounted_products` (integer) How many products in the cart it applied to, on a discount that has been used.
  - `mode` (string) `all`, `none`, or `groups` when it is restricted to some.
  - `groups` (integer[]) User **group** ids, and not Joomla view levels. The two id spaces overlap and disagree, so a value that looks plausible can grant the wrong audience.
  - `mode` (string) `all`, `none`, or `groups` when it is restricted to some.
  - `groups` (integer[]) User **group** ids, and not Joomla view levels. The two id spaces overlap and disagree, so a value that looks plausible can grant the wrong audience.

### Errors

- `not_found` (404) No such discount, or the operator may not change it.
- `code_required` (400) A coupon needs a code, so it cannot be cleared on one.
- `code_taken` (400) Another coupon already uses that code.
- `value_required` (400) A reduction needs a value.
- `save_failed` (500) The shop refused to save it.

---

## Delete a discount or a coupon

`DELETE /discounts/{id}` — scope `write`, since 6.6.0

Deletes a reduction. Orders that already used it keep the amount they were given: the discount on
an order is a figure, not a pointer at this row.

### Path

- `id` (integer, required) The discount or coupon.

### Response

- `deleted` (boolean) True when the row is gone.

### Errors

- `not_found` (404) No such discount, or the operator may not delete it.

---

## Create a customer

`POST /customers` — scope `write`, since 6.6.0

Creates a customer from an email address and a name, as a guest: no account, no password, nothing
for them to log in with.

`POST /customers/{id}/account` turns one into a registered customer afterwards.

### Body

- `email` (string, required) Must not belong to another customer.
- `name` (string) Their display name.

### Response

- `id` (integer) The customer that was created.

### Errors

- `email_taken` (400) Another customer already has that address.
- `save_failed` (500) The shop refused to save it.

---

## Attach a downloadable file

`POST /products/{id}/files` — scope `write`, since 6.6.0

The same as attaching an image, for the files a customer downloads after buying. `free_download`
decides whether they have to buy it first.

### Path

- `id` (integer, required) The product.

### Body

- `data` (string) The bytes, base64 encoded. Give this or `path`.
- `path` (string) A file already in the upload folder.
- `name` (string) The file name to store it under.
- `description` (string) A note on the file.
- `access` (object) Who may download it.

### Response

- `id` (integer) 
- `name` (string) 
- `path` (string) Relative to the upload folder.
- `url` (string) Absolute, ready to display.
- `ordering` (integer) Where it sits among the others; the first image is the one the shop shows.
- `description` (string) The alt text for an image, or a note on a file.
- `access` (object) Who may see or download it.
  - `mode` (string) `all`, `none`, or `groups` when it is restricted to some.
  - `groups` (integer[]) User **group** ids, and not Joomla view levels. The two id spaces overlap and disagree, so a value that looks plausible can grant the wrong audience.
- `free_download` (boolean) Files only: whether it can be downloaded without buying the product.

### Errors

- `not_found` (404) No such product, or the operator may not change it.
- `bad_type` (400) The extension is not one this shop accepts.
- `write_failed` (500) The upload folder refused the file.

---

## A blank address form

`GET /customers/{id}/addresses` — scope `read`, since 6.6.0

The address form of a customer: the shop's own address fields, and the values of the address asked
for. Without an address id it is a blank form, which is what you draw to add one.

The form comes with the values because a shop decides its own address fields, so build from
`fields` rather than assuming a shape.

### Path

- `id` (integer, required) The customer.

### Response

- `address_id` (integer) The address this form is for, `0` for a new one.
- `types` (string[]) Which of `billing` and `shipping` it is used for.
- `default` (string|null) Which kind it is the default for, `null` when it is not a default.
- `fields` (object[]) The shop's address fields, in display order.
- `values` (object) The current values, keyed by field namekey. Empty on a blank form. Keyed by whatever address fields this shop has.
- `country_name` (string) The country spelled out, since the value is a zone namekey.
- `state_name` (string) The state spelled out, empty where the country has none.
  - `namekey` (string) The key its value is stored under.
  - `type` (string) What to render: `text`, `radio`, `singledropdown`, `file`, and the rest.
  - `raw_type` (string) HikaShop's own name for the type.
  - `label` (string) Translated into the operator's language.
  - `default` (string) The value used when none is given.
  - `required` (boolean) Whether the shop refuses to save without it.
  - `options` (object[]) The choices, for a field that has them.
    - `value` (string) What to send back when this choice is picked.
    - `label` (string) What to show.
    - `label_key` (string) The translation key behind the label, when there is one.
  - `multiple` (boolean) Whether more than one may be chosen.
  - `translatable` (boolean) Whether its value can be translated.
  - `upload_dir` (string) For a file field, where its uploads are kept.
  - `allowed_extensions` (string) For a file field, the extensions it accepts. Empty means the shop default.
  - `date_format` (string) For a date field, the format it is stored in.

### Errors

- `not_found` (404) No such customer or address, or the operator may not see them.

---

## An address with its form

`GET /customers/{customerId}/addresses/{addressId}` — scope `read`, since 6.6.0

The address form of a customer: the shop's own address fields, and the values of the address asked
for. Without an address id it is a blank form, which is what you draw to add one.

The form comes with the values because a shop decides its own address fields, so build from
`fields` rather than assuming a shape.

### Path

- `customerId` (integer, required) The customer.
- `addressId` (integer, required) The address.

### Response

- `address_id` (integer) The address this form is for, `0` for a new one.
- `types` (string[]) Which of `billing` and `shipping` it is used for.
- `default` (string|null) Which kind it is the default for, `null` when it is not a default.
- `fields` (object[]) The shop's address fields, in display order.
- `values` (object) The current values, keyed by field namekey. Empty on a blank form. Keyed by whatever address fields this shop has.
- `country_name` (string) The country spelled out, since the value is a zone namekey.
- `state_name` (string) The state spelled out, empty where the country has none.
  - `namekey` (string) The key its value is stored under.
  - `type` (string) What to render: `text`, `radio`, `singledropdown`, `file`, and the rest.
  - `raw_type` (string) HikaShop's own name for the type.
  - `label` (string) Translated into the operator's language.
  - `default` (string) The value used when none is given.
  - `required` (boolean) Whether the shop refuses to save without it.
  - `options` (object[]) The choices, for a field that has them.
    - `value` (string) What to send back when this choice is picked.
    - `label` (string) What to show.
    - `label_key` (string) The translation key behind the label, when there is one.
  - `multiple` (boolean) Whether more than one may be chosen.
  - `translatable` (boolean) Whether its value can be translated.
  - `upload_dir` (string) For a file field, where its uploads are kept.
  - `allowed_extensions` (string) For a file field, the extensions it accepts. Empty means the shop default.
  - `date_format` (string) For a date field, the format it is stored in.

### Errors

- `not_found` (404) No such customer or address, or the operator may not see them.

---

## Add an address

`POST /customers/{id}/addresses` — scope `write`, since 6.6.0

Writes an address of a customer. Without an address id it creates one; with one it saves that one.

`types` says what the address is for, billing or shipping or both, and `default` makes it the
default for those. An address is validated by the shop's own field rules, so a missing required
field is refused rather than half-saved.

### Path

- `id` (integer, required) The customer.

### Body

- `fields` (object, required) Keyed by address field namekey, the same keys the form returned in `values`.
- `types` (string[]) Which of `billing` and `shipping` this address is for. Defaults to both.
- `default` (boolean) Make it the default for its types.

### Response

- `id` (integer) The HikaShop customer id, which is what every customer route takes.
- `cms_id` (integer) The Joomla or WordPress user id, `0` for a guest with no account.
- `name` (string) 
- `email` (string) 
- `username` (string) The login, empty for a guest.
- `type` (string) `registered` or `guest`.
- `blocked` (boolean) Whether the CMS account is disabled.
- `can_edit_account` (boolean) Whether this operator may change the login and password of this particular account. False for an account above their own level, which is how a super user is protected from staff.
- `groups_editable` (boolean) Whether this operator may change which groups the customer is in.
- `groups` (object[]) The groups they are in.
  - `id` (integer) 
  - `title` (string) 
- `available_groups` (object[]) Every group, with whether this operator may put the customer into it, so a picker can grey out the rest rather than offering a refusal.
  - `id` (integer) 
  - `title` (string) 
  - `assignable` (boolean) False for a group this operator may not grant.
- `created` (integer) Unix timestamp of the first time the shop saw them.
- `addresses` (object[]) Their addresses, defaults first.
  - `id` (integer) 
  - `types` (string[]) Which of `billing` and `shipping` it is used for.
  - `name` (string) 
  - `company` (string) 
  - `street` (string) 
  - `city` (string) 
  - `post_code` (string) 
  - `telephone` (string) 
  - `default` (boolean) Whether it is the default for one of its types.
  - `formatted` (object) The address laid out the way this shop lays addresses out, which depends on its address format setting.
- `orders` (object[]) Their orders, newest first, enough to list them.
  - `id` (integer) 
  - `number` (string) The number the customer sees.
  - `status` (string) A namekey.
  - `created` (integer) Unix timestamp.
  - `total` (number) Tax included, in the order currency.
  - `currency_id` (integer) That currency.
- `fields` (object[]) The definitions of your own customer fields.
- `custom_fields` (object) Their values, keyed by namekey. The keys depend on the shop; `fields` says what they are.
- `custom_field_files` (object) For a field holding a file, the file behind the value. Keyed the same way.
    - `text` (string) Several lines, for an invoice or a label.
    - `one_line` (string) One line, for a list.

### Errors

- `not_found` (404) No such customer or address, or the operator may not change them.
- `invalid_fields` (400) A field was rejected by its own rules.
- `invalid_address` (400) The address is not one the shop will accept, usually a missing required field.

---

## Add an address

`PUT /customers/{id}/addresses` — scope `write`, since 6.6.0

Writes an address of a customer. Without an address id it creates one; with one it saves that one.

`types` says what the address is for, billing or shipping or both, and `default` makes it the
default for those. An address is validated by the shop's own field rules, so a missing required
field is refused rather than half-saved.

### Path

- `id` (integer, required) The customer.

### Body

- `fields` (object, required) Keyed by address field namekey, the same keys the form returned in `values`.
- `types` (string[]) Which of `billing` and `shipping` this address is for. Defaults to both.
- `default` (boolean) Make it the default for its types.

### Response

- `id` (integer) The HikaShop customer id, which is what every customer route takes.
- `cms_id` (integer) The Joomla or WordPress user id, `0` for a guest with no account.
- `name` (string) 
- `email` (string) 
- `username` (string) The login, empty for a guest.
- `type` (string) `registered` or `guest`.
- `blocked` (boolean) Whether the CMS account is disabled.
- `can_edit_account` (boolean) Whether this operator may change the login and password of this particular account. False for an account above their own level, which is how a super user is protected from staff.
- `groups_editable` (boolean) Whether this operator may change which groups the customer is in.
- `groups` (object[]) The groups they are in.
  - `id` (integer) 
  - `title` (string) 
- `available_groups` (object[]) Every group, with whether this operator may put the customer into it, so a picker can grey out the rest rather than offering a refusal.
  - `id` (integer) 
  - `title` (string) 
  - `assignable` (boolean) False for a group this operator may not grant.
- `created` (integer) Unix timestamp of the first time the shop saw them.
- `addresses` (object[]) Their addresses, defaults first.
  - `id` (integer) 
  - `types` (string[]) Which of `billing` and `shipping` it is used for.
  - `name` (string) 
  - `company` (string) 
  - `street` (string) 
  - `city` (string) 
  - `post_code` (string) 
  - `telephone` (string) 
  - `default` (boolean) Whether it is the default for one of its types.
  - `formatted` (object) The address laid out the way this shop lays addresses out, which depends on its address format setting.
- `orders` (object[]) Their orders, newest first, enough to list them.
  - `id` (integer) 
  - `number` (string) The number the customer sees.
  - `status` (string) A namekey.
  - `created` (integer) Unix timestamp.
  - `total` (number) Tax included, in the order currency.
  - `currency_id` (integer) That currency.
- `fields` (object[]) The definitions of your own customer fields.
- `custom_fields` (object) Their values, keyed by namekey. The keys depend on the shop; `fields` says what they are.
- `custom_field_files` (object) For a field holding a file, the file behind the value. Keyed the same way.
    - `text` (string) Several lines, for an invoice or a label.
    - `one_line` (string) One line, for a list.

### Errors

- `not_found` (404) No such customer or address, or the operator may not change them.
- `invalid_fields` (400) A field was rejected by its own rules.
- `invalid_address` (400) The address is not one the shop will accept, usually a missing required field.

---

## Save an address

`POST /customers/{customerId}/addresses/{addressId}` — scope `write`, since 6.6.0

Writes an address of a customer. Without an address id it creates one; with one it saves that one.

`types` says what the address is for, billing or shipping or both, and `default` makes it the
default for those. An address is validated by the shop's own field rules, so a missing required
field is refused rather than half-saved.

### Path

- `customerId` (integer, required) The customer.
- `addressId` (integer, required) The address.

### Body

- `fields` (object, required) Keyed by address field namekey, the same keys the form returned in `values`.
- `types` (string[]) Which of `billing` and `shipping` this address is for. Defaults to both.
- `default` (boolean) Make it the default for its types.

### Response

- `id` (integer) The HikaShop customer id, which is what every customer route takes.
- `cms_id` (integer) The Joomla or WordPress user id, `0` for a guest with no account.
- `name` (string) 
- `email` (string) 
- `username` (string) The login, empty for a guest.
- `type` (string) `registered` or `guest`.
- `blocked` (boolean) Whether the CMS account is disabled.
- `can_edit_account` (boolean) Whether this operator may change the login and password of this particular account. False for an account above their own level, which is how a super user is protected from staff.
- `groups_editable` (boolean) Whether this operator may change which groups the customer is in.
- `groups` (object[]) The groups they are in.
  - `id` (integer) 
  - `title` (string) 
- `available_groups` (object[]) Every group, with whether this operator may put the customer into it, so a picker can grey out the rest rather than offering a refusal.
  - `id` (integer) 
  - `title` (string) 
  - `assignable` (boolean) False for a group this operator may not grant.
- `created` (integer) Unix timestamp of the first time the shop saw them.
- `addresses` (object[]) Their addresses, defaults first.
  - `id` (integer) 
  - `types` (string[]) Which of `billing` and `shipping` it is used for.
  - `name` (string) 
  - `company` (string) 
  - `street` (string) 
  - `city` (string) 
  - `post_code` (string) 
  - `telephone` (string) 
  - `default` (boolean) Whether it is the default for one of its types.
  - `formatted` (object) The address laid out the way this shop lays addresses out, which depends on its address format setting.
- `orders` (object[]) Their orders, newest first, enough to list them.
  - `id` (integer) 
  - `number` (string) The number the customer sees.
  - `status` (string) A namekey.
  - `created` (integer) Unix timestamp.
  - `total` (number) Tax included, in the order currency.
  - `currency_id` (integer) That currency.
- `fields` (object[]) The definitions of your own customer fields.
- `custom_fields` (object) Their values, keyed by namekey. The keys depend on the shop; `fields` says what they are.
- `custom_field_files` (object) For a field holding a file, the file behind the value. Keyed the same way.
    - `text` (string) Several lines, for an invoice or a label.
    - `one_line` (string) One line, for a list.

### Errors

- `not_found` (404) No such customer or address, or the operator may not change them.
- `invalid_fields` (400) A field was rejected by its own rules.
- `invalid_address` (400) The address is not one the shop will accept, usually a missing required field.

---

## Save an address

`PUT /customers/{customerId}/addresses/{addressId}` — scope `write`, since 6.6.0

Writes an address of a customer. Without an address id it creates one; with one it saves that one.

`types` says what the address is for, billing or shipping or both, and `default` makes it the
default for those. An address is validated by the shop's own field rules, so a missing required
field is refused rather than half-saved.

### Path

- `customerId` (integer, required) The customer.
- `addressId` (integer, required) The address.

### Body

- `fields` (object, required) Keyed by address field namekey, the same keys the form returned in `values`.
- `types` (string[]) Which of `billing` and `shipping` this address is for. Defaults to both.
- `default` (boolean) Make it the default for its types.

### Response

- `id` (integer) The HikaShop customer id, which is what every customer route takes.
- `cms_id` (integer) The Joomla or WordPress user id, `0` for a guest with no account.
- `name` (string) 
- `email` (string) 
- `username` (string) The login, empty for a guest.
- `type` (string) `registered` or `guest`.
- `blocked` (boolean) Whether the CMS account is disabled.
- `can_edit_account` (boolean) Whether this operator may change the login and password of this particular account. False for an account above their own level, which is how a super user is protected from staff.
- `groups_editable` (boolean) Whether this operator may change which groups the customer is in.
- `groups` (object[]) The groups they are in.
  - `id` (integer) 
  - `title` (string) 
- `available_groups` (object[]) Every group, with whether this operator may put the customer into it, so a picker can grey out the rest rather than offering a refusal.
  - `id` (integer) 
  - `title` (string) 
  - `assignable` (boolean) False for a group this operator may not grant.
- `created` (integer) Unix timestamp of the first time the shop saw them.
- `addresses` (object[]) Their addresses, defaults first.
  - `id` (integer) 
  - `types` (string[]) Which of `billing` and `shipping` it is used for.
  - `name` (string) 
  - `company` (string) 
  - `street` (string) 
  - `city` (string) 
  - `post_code` (string) 
  - `telephone` (string) 
  - `default` (boolean) Whether it is the default for one of its types.
  - `formatted` (object) The address laid out the way this shop lays addresses out, which depends on its address format setting.
- `orders` (object[]) Their orders, newest first, enough to list them.
  - `id` (integer) 
  - `number` (string) The number the customer sees.
  - `status` (string) A namekey.
  - `created` (integer) Unix timestamp.
  - `total` (number) Tax included, in the order currency.
  - `currency_id` (integer) That currency.
- `fields` (object[]) The definitions of your own customer fields.
- `custom_fields` (object) Their values, keyed by namekey. The keys depend on the shop; `fields` says what they are.
- `custom_field_files` (object) For a field holding a file, the file behind the value. Keyed the same way.
    - `text` (string) Several lines, for an invoice or a label.
    - `one_line` (string) One line, for a list.

### Errors

- `not_found` (404) No such customer or address, or the operator may not change them.
- `invalid_fields` (400) A field was rejected by its own rules.
- `invalid_address` (400) The address is not one the shop will accept, usually a missing required field.

---

## Delete an address

`DELETE /customers/{id}/addresses` — scope `write`, since 6.6.0

Removes an address from a customer. Orders that used it keep their own copy of it.

### Path

- `id` (integer, required) The customer.

### Response

- `id` (integer) The HikaShop customer id, which is what every customer route takes.
- `cms_id` (integer) The Joomla or WordPress user id, `0` for a guest with no account.
- `name` (string) 
- `email` (string) 
- `username` (string) The login, empty for a guest.
- `type` (string) `registered` or `guest`.
- `blocked` (boolean) Whether the CMS account is disabled.
- `can_edit_account` (boolean) Whether this operator may change the login and password of this particular account. False for an account above their own level, which is how a super user is protected from staff.
- `groups_editable` (boolean) Whether this operator may change which groups the customer is in.
- `groups` (object[]) The groups they are in.
  - `id` (integer) 
  - `title` (string) 
- `available_groups` (object[]) Every group, with whether this operator may put the customer into it, so a picker can grey out the rest rather than offering a refusal.
  - `id` (integer) 
  - `title` (string) 
  - `assignable` (boolean) False for a group this operator may not grant.
- `created` (integer) Unix timestamp of the first time the shop saw them.
- `addresses` (object[]) Their addresses, defaults first.
  - `id` (integer) 
  - `types` (string[]) Which of `billing` and `shipping` it is used for.
  - `name` (string) 
  - `company` (string) 
  - `street` (string) 
  - `city` (string) 
  - `post_code` (string) 
  - `telephone` (string) 
  - `default` (boolean) Whether it is the default for one of its types.
  - `formatted` (object) The address laid out the way this shop lays addresses out, which depends on its address format setting.
- `orders` (object[]) Their orders, newest first, enough to list them.
  - `id` (integer) 
  - `number` (string) The number the customer sees.
  - `status` (string) A namekey.
  - `created` (integer) Unix timestamp.
  - `total` (number) Tax included, in the order currency.
  - `currency_id` (integer) That currency.
- `fields` (object[]) The definitions of your own customer fields.
- `custom_fields` (object) Their values, keyed by namekey. The keys depend on the shop; `fields` says what they are.
- `custom_field_files` (object) For a field holding a file, the file behind the value. Keyed the same way.
    - `text` (string) Several lines, for an invoice or a label.
    - `one_line` (string) One line, for a list.

### Errors

- `not_found` (404) No such customer or address, or the operator may not change them.

---

## Delete an address

`DELETE /customers/{customerId}/addresses/{addressId}` — scope `write`, since 6.6.0

Removes an address from a customer. Orders that used it keep their own copy of it.

### Path

- `customerId` (integer, required) The customer.
- `addressId` (integer, required) The address.

### Response

- `id` (integer) The HikaShop customer id, which is what every customer route takes.
- `cms_id` (integer) The Joomla or WordPress user id, `0` for a guest with no account.
- `name` (string) 
- `email` (string) 
- `username` (string) The login, empty for a guest.
- `type` (string) `registered` or `guest`.
- `blocked` (boolean) Whether the CMS account is disabled.
- `can_edit_account` (boolean) Whether this operator may change the login and password of this particular account. False for an account above their own level, which is how a super user is protected from staff.
- `groups_editable` (boolean) Whether this operator may change which groups the customer is in.
- `groups` (object[]) The groups they are in.
  - `id` (integer) 
  - `title` (string) 
- `available_groups` (object[]) Every group, with whether this operator may put the customer into it, so a picker can grey out the rest rather than offering a refusal.
  - `id` (integer) 
  - `title` (string) 
  - `assignable` (boolean) False for a group this operator may not grant.
- `created` (integer) Unix timestamp of the first time the shop saw them.
- `addresses` (object[]) Their addresses, defaults first.
  - `id` (integer) 
  - `types` (string[]) Which of `billing` and `shipping` it is used for.
  - `name` (string) 
  - `company` (string) 
  - `street` (string) 
  - `city` (string) 
  - `post_code` (string) 
  - `telephone` (string) 
  - `default` (boolean) Whether it is the default for one of its types.
  - `formatted` (object) The address laid out the way this shop lays addresses out, which depends on its address format setting.
- `orders` (object[]) Their orders, newest first, enough to list them.
  - `id` (integer) 
  - `number` (string) The number the customer sees.
  - `status` (string) A namekey.
  - `created` (integer) Unix timestamp.
  - `total` (number) Tax included, in the order currency.
  - `currency_id` (integer) That currency.
- `fields` (object[]) The definitions of your own customer fields.
- `custom_fields` (object) Their values, keyed by namekey. The keys depend on the shop; `fields` says what they are.
- `custom_field_files` (object) For a field holding a file, the file behind the value. Keyed the same way.
    - `text` (string) Several lines, for an invoice or a label.
    - `one_line` (string) One line, for a list.

### Errors

- `not_found` (404) No such customer or address, or the operator may not change them.

---

## Redeem a pairing code

`POST /pair` — public, no token, since 6.6.0

Exchanges a single-use code, generated in **System > App Devices** of the shop's backend, for the
device's own token. This is the only route that answers without one.

The token comes back **once**. Only its hash is stored, so a lost token is re-paired, never
recovered. Attempts are rate limited per address.

### Body

- `code` (string, required) The pairing code as shown in the backend. Case and spacing are normalised, so the grouping dash is optional.
- `device_name` (string) What to call this device in the device list. Defaults to `Device`.
- `platform` (string) A free label kept for the listing. Anything that is not a letter, a digit, a dash or an underscore is stripped.

### Response

- `token` (string) Send this as the bearer token from now on. It is not retrievable again.
- `scopes` (string[]) `read`, and `write` when the code granted it.
- `device_id` (integer) The row in the device list, which is what you revoke later.

### Errors

- `invalid_request` (400) No code was sent.
- `invalid_code` (403) The code is unknown, already used, or expired.
- `too_many_requests` (429) Too many attempts from this address.

---

## List products

`GET /products` — scope `read`, since 6.6.0

The catalogue as the backend sees it, unpublished products included, which is what makes this
different from the front end.

Variants are not listed on their own. A parent carries `has_variants`, and
`GET /products/{id}` returns the variants themselves.

### Query

- `start` (integer) Offset into the result set. Defaults to `0`.
- `limit` (integer) Page size. Defaults to `20` and is capped at `100`.
- `search` (string) Matches the product name and the product code.
- `ids` (string) Comma separated product ids, to resolve a known set in one call.
- `category_id` (integer) Restrict to one category.

### Response

- `id` (integer) 
- `name` (string) 
- `code` (string) The SKU. Unique within the shop.
- `quantity` (integer) `-1` when the product does not track stock, which is not the same as `0`.
- `published` (boolean) 
- `has_variants` (boolean) Ask `GET /products/{id}` for the variants themselves.
- `image` (string|null) Absolute URL of the main image, or `null`.
- `price` (number|null) `null` when the product has no price row at all.
- `currency_id` (integer) A row in the shop's currency table, not an ISO code.
- `custom_fields` (object) The listing values of this product, keyed by field namekey. The keys are whatever this shop has configured, so there is no list to give. `fields` in the envelope says what they are.

---

## List orders

`GET /orders` — scope `read`, since 6.6.0

The orders the operator may see, newest first. It is the listing the app's order screen is built
on, so it carries just enough to draw a row and no more; ask for one order when you need the rest.

### Query

- `start` (integer) Offset. Defaults to `0`.
- `limit` (integer) Defaults to `20`, capped at `100`.
- `search` (string) Matches the order number and the customer.
- `status` (string) A status namekey, as listed by `GET /statuses`.

### Response

- `id` (integer) The order id, which is what every other order route takes.
- `number` (string) The order number the customer sees, which is not the id.
- `status` (string) A namekey, not a label. `GET /statuses` translates it.
- `created` (integer) Unix timestamp.
- `total` (number) What the customer owes, tax included, in the order currency.
- `currency_id` (integer) The order keeps the currency it was placed in, which need not be the shop default.
- `customer` (object) Enough to name the buyer in a list.
  - `name` (string) 
  - `email` (string) 
- `custom_fields` (object) The listing values of your own order fields, keyed by namekey. The keys are whatever this shop has configured; `fields` in the envelope says what they are.

---

## Read one order

`GET /orders/{id}` — scope `read`, since 6.6.0

The whole order: its lines, its totals, its addresses, its history and your own fields.

Money is in the currency the order was placed in, which is not necessarily the shop's. Do not
convert it: an order is a record of what was agreed at the time.

### Path

- `id` (integer, required) The order id.

### Response

- `id` (integer) 
- `number` (string) The number the customer sees.
- `status` (string) A namekey.
- `created` (integer) Unix timestamp.
- `modified` (integer) Unix timestamp of the last change.
- `currency_id` (integer) The currency the order was placed in.
- `totals` (object) The figures. See the shape below.
- `customer` (object) Who placed it.
  - `name` (string) 
  - `email` (string) 
- `payment_method` (string) How it was paid, as the shop names it.
- `shipping_method` (string) How it ships.
- `invoice_number` (string) Empty until an invoice has been issued.
- `invoice_created` (integer|null) Unix timestamp of the invoice.
- `items` (object[]) The lines: `id`, `name`, `code`, `quantity`, `price`, `tax` and whether the line can still be edited.
- `billing_address` (object|null) The address as it was at the time, `null` when there is none. It is a copy, not a pointer at the customer's current address.
- `shipping_address` (object|null) The same, for delivery.
- `shipping_address_override` (boolean) Whether the delivery address was set apart from the billing one.
- `history` (object[]) What has happened to the order, oldest first.
  - `status` (string) The namekey it moved to.
  - `created` (integer) Unix timestamp.
  - `type` (string) What caused it: a payment notification, an operator, the shop itself.
  - `reason` (string) The note recorded with the change, when there was one.
  - `notified` (boolean) Whether the customer was emailed about it.
- `fields` (object[]) The definitions of your own order fields.
- `custom_fields` (object) Their values, keyed by namekey. The keys depend on the shop; `fields` says what they are.
- `custom_field_files` (object) For a field holding a file, the file behind the value. Keyed the same way.
- `fees` (object) The `discount`, `shipping` and `payment` amounts, which is what `PUT /orders/{id}/fees` writes.
- `tax_rates` (object[]) The rates that made up the tax, each with its namekey and rate, so a total can be explained rather than only shown.
  - `total` (number) What the customer owes, tax included.
  - `discount` (number) The discount applied, as a positive figure already subtracted.
  - `shipping` (number) The shipping charged.
  - `payment` (number) The payment fee charged.
  - `tax` (number) The tax within the total, not on top of it.
  - `id` (integer) The line id, which is what `PUT /orders/{id}/products/{lineId}` takes. It is not the product id.
  - `name` (string) The product as it was named when ordered, which may since have changed.
  - `code` (string) Its SKU at the time.
  - `quantity` (integer) 
  - `price` (number) Unit price, tax excluded, as agreed at the time.
  - `tax` (number) Tax on the line.
  - `editable` (boolean) False once the line can no longer be changed, for instance on a shipped order.
  - `discount` (object) Its `amount`, its `tax`, the `tax_namekeys` behind that tax, and the coupon `code` when one was used.
    - `amount` (number) A positive figure, already subtracted from the total.
    - `tax` (number) The tax on it.
    - `tax_namekeys` (string[]) Which tax rates that came from.
    - `code` (string) The coupon code, empty for a discount applied by hand.
  - `shipping` (object) The shipping charge and what carried it.
    - `amount` (number) Tax excluded.
    - `tax` (number) The tax on it.
    - `tax_namekeys` (string[]) Which tax rates that came from.
    - `method` (string) The plugin that handled it.
    - `method_name` (string) As the merchant named it.
  - `payment` (object) The payment fee and what took it.
    - `amount` (number) Tax excluded.
    - `tax` (number) The tax on it.
    - `tax_namekeys` (string[]) Which tax rates that came from.
    - `method` (string) The plugin that took it.
    - `method_name` (string) As the merchant named it.
  - `namekey` (string) The tax rate as the shop names it.
  - `rate` (number) As a fraction, so `0.1` is ten percent.

---

## Set the order fees

`PUT /orders/{id}/fees` — scope `write`, since 6.6.0

Replaces the discount, shipping and payment amounts and re-totals the order, so a client does not
have to compute a total itself and risk disagreeing with the shop.

What you do not send keeps its current value.

### Path

- `id` (integer, required) The order id.

### Body

- `fees` (object, required) Any of `discount`, `shipping` and `payment`, each an object with at least an `amount`, tax excluded. A discount is a positive figure and is subtracted.

### Response

- `id` (integer) 
- `fees` (object) The discount, shipping and payment amounts of the order.
- `totals` (object) The order totalled, so a client need not compute it and disagree with the shop.
  - `discount` (object) Its `amount`, its `tax`, the `tax_namekeys` behind that tax, and the coupon `code` when one was used.
    - `amount` (number) A positive figure, already subtracted from the total.
    - `tax` (number) The tax on it.
    - `tax_namekeys` (string[]) Which tax rates that came from.
    - `code` (string) The coupon code, empty for a discount applied by hand.
  - `shipping` (object) The shipping charge and what carried it.
    - `amount` (number) Tax excluded.
    - `tax` (number) The tax on it.
    - `tax_namekeys` (string[]) Which tax rates that came from.
    - `method` (string) The plugin that handled it.
    - `method_name` (string) As the merchant named it.
  - `payment` (object) The payment fee and what took it.
    - `amount` (number) Tax excluded.
    - `tax` (number) The tax on it.
    - `tax_namekeys` (string[]) Which tax rates that came from.
    - `method` (string) The plugin that took it.
    - `method_name` (string) As the merchant named it.
  - `total` (number) What the customer owes, tax included.
  - `discount` (number) The discount applied, as a positive figure already subtracted.
  - `shipping` (number) The shipping charged.
  - `payment` (number) The payment fee charged.
  - `tax` (number) The tax within the total, not on top of it.

### Errors

- `not_found` (404) No such order, or the operator may not change it.
- `save_failed` (500) The order could not be saved.

---

## An address of an order, with its form

`GET /orders/{id}/address/{type}` — scope `read`, since 6.6.0

The billing or shipping address of an order, together with the form the shop would use to edit it.

The form is sent with the values because a shop decides its own address fields: which exist, which
are required, and in what order. Build the form from `fields` rather than assuming a shape, and
`PUT` the same keys back.

### Path

- `id` (integer, required) The order id.
- `type` (string, required) `billing` or `shipping`.

### Response

- `type` (string) Which address this is.
- `address_id` (integer) The address row, `0` when the order has none of that kind.
- `fields` (object[]) The shop's address fields, in display order.
- `values` (object) The current values, keyed by field namekey. Keyed by whatever address fields this shop has.
- `country_name` (string) The country spelled out, since the value itself is a zone id.
- `state_name` (string) The state spelled out, empty where the country has none.
  - `namekey` (string) The key to send the value back under.
  - `type` (string) What to render: `text`, `zone`, `singledropdown`, and the rest.
  - `raw_type` (string) HikaShop's own name for the type.
  - `label` (string) Translated into the operator's language.
  - `default` (string) The value used when none is given.
  - `required` (boolean) Whether the shop refuses to save the address without it.
  - `options` (object[]) The choices, for a field that has them. A country or a state is filled from the zones rather than from here.
    - `value` (string) What to send back when this choice is picked.
    - `label` (string) What to show.
    - `label_key` (string) The translation key behind the label, when there is one.
  - `multiple` (boolean) Whether more than one may be chosen.
  - `translatable` (boolean) Not meaningful on an address, where values are the customer's own words.
  - `upload_dir` (string) Unused on an address field.
  - `allowed_extensions` (string) Unused on an address field.
  - `date_format` (string) For a date field, the format it is stored in.

### Errors

- `not_found` (404) No such order, or the operator may not see it.

---

## The coupons that can be applied

`GET /coupons` — scope `read`, since 6.6.0

The shop's published coupons, so an operator can pick one rather than remember a code. Applying it
is a separate call, and the shop validates it again there: a coupon listed here can still be
refused for this particular order.

### Response

- `id` (integer) 
- `code` (string) What the customer would type. This is what you send to apply it.
- `flat_amount` (number) A fixed reduction, `0` when the coupon is a percentage.
- `percent_amount` (number) A percentage reduction, `0` when the coupon is a fixed amount.
- `currency_id` (integer) The currency a flat amount is expressed in.
- `start` (integer|null) Unix timestamp before which it is not valid.
- `end` (integer|null) Unix timestamp after which it expires.
- `quota` (integer) How many times it may be used in total, `0` for no limit.
- `used_times` (integer) How many times it already has been.
- `minimum_order` (number) The order total below which it does not apply, `0` for none.

---

## Change an order's status

`POST /orders/{id}/status` — scope `write`, since 6.6.0

Moves the order and, when asked, sends the customer the same notification the backend would have
sent.

This calls the same code the backend does, so stock, invoices and every plugin listening on a
status change behave exactly as they do there. It is not a database update.

### Path

- `id` (integer, required) The order id.

### Body

- `status` (string, required) A status **namekey**, as listed by `GET /statuses`. Not the translated label.
- `notify` (boolean) Send the customer the notification for the new status. Defaults to `false`.
- `reason` (string) Recorded in the order history, and included in the notification when there is one.

### Response

- `id` (integer) The order id.
- `status` (string) The namekey the order now has.
- `changed` (boolean) False when the order already had that status.
- `notified` (boolean) Whether the customer was actually emailed, which can be false even when you asked, if the status has no notification configured.

### Errors

- `invalid_status` (400) No such status namekey on this shop.
- `not_found` (404) No such order, or the operator may not see it.
- `save_failed` (500) The order could not be saved.

---

