Evyoo POS API v1

Products

List products, services and special or assembled items, with prices and stock

The product catalog. Only active products are listed unless you ask for status=all or status=inactive.

Prices and stock are computed for you. A client with no price list restriction gets the base price and a price_lists array with the price of each of the seven price lists. A client restricted to one price list gets a single price: the one of its list, or the base price when the list price is zero. Stock is listed per store; for a client restricted to some stores, the totals add up those stores only.

needs products:read Catalog and parties

Endpoints#

List: GET/api/v1/products   One record: GET/api/v1/products/{id}

Example request#

List with a page size and a sort:

bash
curl "https://votre-entreprise.evyooht.com/api/v1/products?per_page=5&sort=-name" \
  -H "Authorization: Bearer evy_at_votre_jeton"
powershell
$headers = @{ Authorization = "Bearer evy_at_votre_jeton" }
Invoke-RestMethod -Uri "https://votre-entreprise.evyooht.com/api/v1/products?per_page=5&sort=-name" -Headers $headers
javascript
const response = await fetch("https://votre-entreprise.evyooht.com/api/v1/products?per_page=5&sort=-name", {
  headers: { Authorization: "Bearer evy_at_votre_jeton" },
});
const data = await response.json();
console.log(data);
php
$ch = curl_init("https://votre-entreprise.evyooht.com/api/v1/products?per_page=5&sort=-name");
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => ["Authorization: Bearer evy_at_votre_jeton"],
]);
$data = json_decode(curl_exec($ch), true);
curl_close($ch);
print_r($data);
python
import requests

response = requests.get(
    "https://votre-entreprise.evyooht.com/api/v1/products",
    params={"per_page": 5, "sort": "-name"},
    headers={"Authorization": "Bearer evy_at_votre_jeton"},
)
print(response.json())

One record by its identifier:

bash
curl "https://votre-entreprise.evyooht.com/api/v1/products/463" \
  -H "Authorization: Bearer evy_at_votre_jeton"
powershell
$headers = @{ Authorization = "Bearer evy_at_votre_jeton" }
Invoke-RestMethod -Uri "https://votre-entreprise.evyooht.com/api/v1/products/463" -Headers $headers
javascript
const response = await fetch("https://votre-entreprise.evyooht.com/api/v1/products/463", {
  headers: { Authorization: "Bearer evy_at_votre_jeton" },
});
const data = await response.json();
console.log(data);
php
$ch = curl_init("https://votre-entreprise.evyooht.com/api/v1/products/463");
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => ["Authorization: Bearer evy_at_votre_jeton"],
]);
$data = json_decode(curl_exec($ch), true);
curl_close($ch);
print_r($data);
python
import requests

response = requests.get(
    "https://votre-entreprise.evyooht.com/api/v1/products/463",
    headers={"Authorization": "Bearer evy_at_votre_jeton"},
)
print(response.json())

Query parameters of the list#

All filters can be combined: they add up (AND). An unknown parameter or an invalid value is refused with a 400 error, never ignored.

ParameterTypeDescription
pageintegerPage number, starting at 1. A page beyond the data is empty, not an error. At most 10000 pages: filter instead of paging deeper. Default: 1.
per_pageintegerResults per page, from 1 to 100. Default: 25.
sortstringSort fields, separated by commas, with - in front for descending order. Up to 3 fields. Sortable fields: id, name, code, updated_at. Default: name. Default: name.
statusstringactive (default), inactive, or all. When omitted, only active records are listed. Allowed values: active, inactive, all.
typestringFilter by type. Several values can be given, separated by commas. Allowed values: inventory, service, assembly, special.
department_idintegerOnly products of this department.
sub_department_idintegerOnly products of this sub-department.
vendor_idintegerOnly records of this vendor.
searchstringText contained in the name, the code or the barcode.
updated_sincestringOnly records modified since this date (YYYY-MM-DD) or date and time (YYYY-MM-DDTHH:MM:SS).

Response fields#

The list and the detail route return these fields. A field is null when the value is not recorded. A field marked needs permission is absent (not empty) unless your client holds that permission.

FieldTypeDescription
idintegerUnique identifier. Use it in the detail route.
codestring| nullProduct code (item code).
barcodestring| nullBarcode, when one is recorded.
namestring| nullProduct name.
descriptionstring| nullProduct description.
type"inventory"| "service" | "assembly" | "special" | nullKind of item. inventory: stocked product. service: not stocked. assembly: built from other products. special: special item.
status"active"| "inactive" | nullactive or inactive.
sizestring| nullSize, when recorded.
attributestring| nullFree attribute (color, variant…), when recorded.
department_idinteger| nullIdentifier of the department (see the departments resource).
departmentstring| nullName of the department.
sub_department_idinteger| nullIdentifier of the sub-department (see the sub-departments resource).
sub_departmentstring| nullName of the sub-department.
unitstring| nullMain unit of sale (for example Box of 100).
second_unitstring| nullSecond unit of sale, when the product can be sold in two units.
vendor_idinteger| nullIdentifier of the usual vendor. 0 when none.
reorder_pointnumber| nullStock level under which the product should be reordered.
currencystring| nullCurrency of the product prices, when recorded.
cost needs costs:readnumber| nullLast purchase cost. Requires costs:read.
average_cost needs costs:readnumber| nullWeighted average cost. Requires costs:read.
order_cost needs costs:readnumber| nullCost used on purchase orders. Requires costs:read.
created_atstring(date-time, local) | nullWhen the record was created (local time, no time zone).
updated_atstring(date-time, local) | nullWhen the record was last modified (local time, no time zone). Use it with the updated_since filter to synchronize.
quantity_totalnumber| nullQuantity in stock. For a client restricted to some stores, the sum of those stores only.
on_sales_ordernumber| nullQuantity reserved on open sales orders (same restriction as quantity_total).
on_purchase_ordernumber| nullQuantity expected on open purchase orders (same restriction as quantity_total).
pricenumber| nullSelling price applicable to your client.
second_unit_pricenumber| nullSelling price of the second unit applicable to your client.
price_listsarrayof objectPrice of each price list. Absent when your client is restricted to one price list.
stockarrayof objectStock per store, limited to the stores your client may see.

Example response: list#

json
{
  "data": [
    {
      "id": 463,
      "code": "PRD-0463",
      "barcode": "6291041500213",
      "name": "Thermal paper roll 57x18m",
      "description": "Thermal paper roll for receipt printers",
      "type": "inventory",
      "status": "active",
      "size": "",
      "attribute": "",
      "department_id": 3,
      "department": "Office supplies",
      "sub_department_id": 21,
      "sub_department": "Paper",
      "unit": "Box of 100",
      "second_unit": "Box of 200",
      "vendor_id": 12,
      "reorder_point": 20,
      "currency": "USD",
      "cost": 90,
      "average_cost": 92.5,
      "order_cost": 90,
      "created_at": "2026-03-15T14:30:00",
      "updated_at": "2026-03-15T14:30:00",
      "quantity_total": 12,
      "on_sales_order": 4,
      "on_purchase_order": 0,
      "price": 125,
      "second_unit_price": 250,
      "price_lists": [
        {
          "price_list_id": 1,
          "price": 125,
          "second_unit_price": 250
        },
        {
          "price_list_id": 2,
          "price": 118,
          "second_unit_price": 236
        }
      ],
      "stock": [
        {
          "store_id": 1,
          "quantity": 8,
          "on_sales_order": 3,
          "on_purchase_order": 0
        },
        {
          "store_id": 2,
          "quantity": 4,
          "on_sales_order": 1,
          "on_purchase_order": 0
        }
      ]
    }
  ],
  "meta": {
    "page": 1,
    "per_page": 25,
    "total": 1,
    "total_pages": 1
  }
}

Example response: one record#

json
{
  "data": {
    "id": 463,
    "code": "PRD-0463",
    "barcode": "6291041500213",
    "name": "Thermal paper roll 57x18m",
    "description": "Thermal paper roll for receipt printers",
    "type": "inventory",
    "status": "active",
    "size": "",
    "attribute": "",
    "department_id": 3,
    "department": "Office supplies",
    "sub_department_id": 21,
    "sub_department": "Paper",
    "unit": "Box of 100",
    "second_unit": "Box of 200",
    "vendor_id": 12,
    "reorder_point": 20,
    "currency": "USD",
    "cost": 90,
    "average_cost": 92.5,
    "order_cost": 90,
    "created_at": "2026-03-15T14:30:00",
    "updated_at": "2026-03-15T14:30:00",
    "quantity_total": 12,
    "on_sales_order": 4,
    "on_purchase_order": 0,
    "price": 125,
    "second_unit_price": 250,
    "price_lists": [
      {
        "price_list_id": 1,
        "price": 125,
        "second_unit_price": 250
      },
      {
        "price_list_id": 2,
        "price": 118,
        "second_unit_price": 236
      }
    ],
    "stock": [
      {
        "store_id": 1,
        "quantity": 8,
        "on_sales_order": 3,
        "on_purchase_order": 0
      },
      {
        "store_id": 2,
        "quantity": 4,
        "on_sales_order": 1,
        "on_purchase_order": 0
      }
    ]
  }
}

Errors#

The error format and the full list of codes are described in Errors.

StatusWhen
400A query parameter is invalid or unknown. Every fault is reported at once in error.details. Nothing is ignored silently.
401The token is missing, malformed, expired or revoked (missing_token or invalid_token). Ask for a new token.
403Your token does not carry the permission this route needs (insufficient_scope), or the request was not made over HTTPS (https_required).
429Too many requests this minute. Wait for the number of seconds in the Retry-After header.