New product creation
Business scenario
When a merchant adds a product to their integrated system — an e-commerce platform, a POS system, or any other catalog source — that product must also exist in Qoyod before it can appear on invoices, be tracked across inventory locations, or have its cost and revenue correctly posted to the chart of accounts.
This use case covers the integrated system to Qoyod direction: your integration detects
that a new product has been created in the integrated system, then sends a request to
Qoyod to create a matching record. The result is a Qoyod product with a numeric id that
your integration stores and reuses for all future operations on that record.
If this step is skipped or fails silently, Qoyod has no record of the product. Subsequent invoice creation fails because it cannot reference a product that does not exist. Inventory tracking cannot start. Accounting entries for sales and cost of goods are not posted. The longer the gap goes unaddressed, the more remediation work is required to backfill the missing records.
When to use this
Use this when:
- A new product is added to your integrated system and no corresponding Qoyod record yet exists for it.
- You are creating a product for the first time — there is no prior Qoyod ID stored against this item.
- You need to establish the product's
typeclassification, which can only be set at creation time.
Do not use this when:
- The product already exists in Qoyod and you need to change its name, price, or other fields — that is product update (UC-02).
- You are migrating an existing catalog of many products at once — that is initial product import (UC-07), which covers batch creation considerations.
- You need to set or adjust stock quantities — stock is managed through inventory adjustments, not on product creation.
Prerequisites
- A valid OAuth 2.0 access token for the Qoyod account you are integrating with.
- If you plan to assign the product to a category, the category must already exist in
Qoyod. Retrieve its integer
idfrom Qoyod before sending the product creation request. Category creation is out of scope for this page. - If you plan to associate a tax rate, the tax record must already exist in Qoyod. Retrieve
its integer
idbefore sending the request. - A reliable mechanism in your integration to capture and store the Qoyod product
idreturned in the 201 Created response. This ID is required for all future updates and references to this product.
Sequence diagram
Step-by-step
1. Build the request body
Wrap the product fields in a product object. At minimum, send en_name or name (the
Arabic name) so the record is identifiable. Include type to classify the product
correctly — the type field is ignored on update, so it must be set accurately here.
{
"product": {
"en_name": "Blue Ceramic Mug",
"name": "كوب سيراميك أزرق",
"type": "Product",
"sku": "MUG-BLUE-001",
"selling_price": 25.00,
"buying_price": 12.00,
"track_quantity": 1,
"category_id": 42
}
}
The selling_price field, when present, causes Qoyod to set is_sold: true on the
record automatically. The buying_price field, when present, causes Qoyod to set
is_bought: true. You do not need to set these boolean fields explicitly.
2. Send the request
Send a POST request to https://api.qoyod.com/2.0/products with the Authorization
header carrying your access token.
curl -X POST https://api.qoyod.com/2.0/products \
-H "Authorization: Bearer {access_token}" \
-H "Content-Type: application/json" \
-d '{
"product": {
"en_name": "Blue Ceramic Mug",
"name": "كوب سيراميك أزرق",
"type": "Product",
"sku": "MUG-BLUE-001",
"selling_price": 25.00,
"buying_price": 12.00,
"track_quantity": 1,
"category_id": 42
}
}'
3. Handle the 201 Created response
A successful single-product creation returns 201 Created with the new product record
nested under a product key. Extract and store the id field immediately — it is the
Qoyod product ID your integration uses for every future operation on this record.
{
"product": {
"id": 1087,
"name_ar": "كوب سيراميك أزرق",
"name_en": "Blue Ceramic Mug",
"type": "Product",
"sku": "MUG-BLUE-001",
"selling_price": 25.0,
"buying_price": 12.0,
"is_sold": true,
"is_bought": true,
"track_quantity": 1,
"category_id": 42,
"inventories": [],
"created_at": "2026-05-17T10:23:00.000Z"
}
}
Store the value of id — in this example, 1087 — on the corresponding record in your
integrated system. The inventories array is empty at creation because no stock
transactions have occurred yet.
4. Verify the record (optional but recommended)
After storing the Qoyod ID, retrieve the product to confirm the record is complete and that Qoyod interpreted all fields as intended. See the Verification section below.
Field mapping
The table below maps common integrated system concepts to the corresponding Qoyod fields
for the product creation request. All field names match the POST /2.0/products request
body exactly.
| Integrated system concept | Qoyod field | Required | Notes |
|---|---|---|---|
| Product name (English) | en_name | Recommended | Also accepted as name_en. Send in all cases where an English name exists. |
| Product name (Arabic) | name | Recommended | Also accepted as name_ar or ar_name. At least one name field should be sent. |
| Product type classification | type | No (defaults to Product) | Must be set correctly at creation; ignored on update. See type selection guidance below. |
| SKU | sku | No | Pass through from the integrated system. |
| Selling price | selling_price | No | Numeric. Automatically sets is_sold: true on the record. |
| Buying price | buying_price | No | Numeric. Automatically sets is_bought: true on the record. |
| Inventory tracking enabled | track_quantity | No | 0 = untracked (default). 1 = Qoyod manages stock levels for this product. |
| Category | category_id | No | Integer. Must reference an existing Qoyod category ID. |
| Tax | tax_id | No | Integer. Must reference an existing Qoyod tax ID. |
For the complete list of fields accepted by POST /2.0/products, see the
products reference.
Type selection guidance. Choose type based on what the product record represents:
Product— a physical good that can be bought and sold. This is the default whentypeis omitted.Service— an intangible offering that can be sold but does not carry stock.Expense— a cost item that is not sold to customers.RawMaterial— a component used as input in a Recipe.Recipe— a compound product assembled from RawMaterial ingredients.
Verification
After a successful 201 Created response, retrieve the product using
GET /2.0/products/{id} to confirm the record exists in Qoyod with the values you sent.
curl -X GET https://api.qoyod.com/2.0/products/{product_id} \
-H "Authorization: Bearer {access_token}"
A successful retrieval returns 200 OK with a product object. Confirm:
- The
idfield is present and matches the value you stored. name_enandname_armatch what you sent.typereflects the value you set on creation.track_quantityis1if you intend to track stock for this product.skumatches what you sent, if provided.
If any field does not match your expectation, check whether the field name you used in the
request body is a supported alias. For example, name_ar and ar_name are both accepted
aliases for the Arabic name field on POST, but the response always returns the value as
name_ar.
The inventories array in the response will be empty immediately after creation. Stock
levels appear here only after stock-affecting transactions — invoices, bills, stock takes,
or inventory transfers — have been recorded in Qoyod for this product.
Error scenarios
| Status code | When it occurs | What to do |
|---|---|---|
422 Unprocessable Entity | Qoyod could not create the record due to a validation failure. The response body contains an errors object where keys are field names and values are arrays of error strings. | Read the errors object to identify which fields failed validation. Correct the field values in your request body and retry. |
422 Unprocessable Entity | The category_id you sent does not reference an existing Qoyod category. | Verify the category ID by querying the categories endpoint. Ensure the category was created before this product creation request. |
401 Unauthorized | The access token is missing, expired, or invalid. | Refresh the OAuth 2.0 access token and retry the request. |
For retry logic, exponential backoff guidance, and handling transient errors, see error handling.