Create a sale
POST /sales/ordersRequires sales.orders:write and API key authentication. The creator is recorded as API · integration name; no staff session or seller configured on the credential is required.
Supported flow
Creates drafts only, without payments, manual discounts, fiscal issuance or inventory processing. Prices must match the applicable catalog for the company, customer, currency, channel and unit of measure. Different prices return 403, even if the seller has internal price-editing permissions.
Retries and external references
Send a stable external_id for each sale and reuse it when retrying after a timeout. The first creation returns 201; the same reference with the same API key returns 409 with code: "external_id_conflict", without creating or modifying the existing sale, even if the body changes. The constraint also covers concurrent requests. Another API key may use the same reference. Omitting the field or sending null allows each request to create a new sale.
Request fields
| Field | Type | Requirement |
|---|---|---|
origin_warehouse_id | integer | Required; existing warehouse. |
issue_date | string | Required, YYYY-MM-DD. |
document_code | string | Required; existing document type code. |
currency | string | Required; existing three-character currency code. |
sale_channel_code | string | Required; existing sales channel code. |
payment_term_id | integer | Required; existing payment term. |
include_tax | boolean | Required; whether prices include tax. |
source | string | Optional; only api, set by the server if omitted. |
is_draft | boolean | Optional; only true, default true. |
process_inventory | boolean | Optional; only false, default false. |
external_id | string or null | Optional, up to 255 characters; unique per API key. Reusing it returns 409. |
exchange_rate | number | Optional, positive; default 1. |
seller_id | integer or null | Optional active company member; also requires sales.orders:assign-seller. Does not change the creator. |
notes | string or null | Optional, up to 5000 characters. |
customer | object | Required; see below. |
items | array | Required; 1–200 items. |
Customer requires name (up to 255 characters), tax_id (up to 50), and an existing identity_document_type_code. Optional: valid email, billing_address and shipping_address; each address is an object with an address string of up to 255 characters, or null.
Each item requires sku (existing product), name (up to 255 characters), positive qty, non-negative price, existing uom_code, tax_exemption_code and at least one existing tax code in tax_codes. Tax codes are trimmed, uppercased and deduplicated. ICBPER also requires IGV.
Optional external actor
Send X-External-Actor-Id to retain an external user reference for auditing (up to 255 characters, no controls). It is declared by the integration and never authenticates a user or changes permissions. See identity and traceability.
Example
Save as sale.json. IDs, SKU, document, unit and channel codes are illustrative and must exist in your company. The price must match your catalog.
{
"origin_warehouse_id": 1,
"external_id": "integracion-venta-0001",
"issue_date": "2026-11-05",
"document_code": "01",
"currency": "PEN",
"sale_channel_code": "DIRECT",
"source": "api",
"payment_term_id": 1,
"include_tax": true,
"process_inventory": false,
"is_draft": true,
"customer": {
"name": "Empresa de ejemplo SAC",
"identity_document_type_code": "RUC",
"tax_id": "20123456789",
"email": "integraciones@example.com",
"billing_address": {
"address": "Av. Ejemplo 123, Lima"
},
"shipping_address": null
},
"items": [
{
"sku": "PRODUCTO-001",
"name": "Producto de ejemplo",
"qty": 1,
"uom_code": "NIU",
"price": 118,
"tax_exemption_code": "10",
"tax_codes": ["IGV"]
}
],
"notes": "Venta creada desde una integración"
}curl --request POST 'https://api.simple.pe/sales/orders' \
--header 'Accept: application/json' \
--header 'Content-Type: application/json' \
--header "X-Api-Key: ${SIMPLE_API_KEY}" \
--header "X-Api-Secret: ${SIMPLE_API_SECRET}" \
--data-binary @sale.jsonResponse · 201 Created
Illustrative excerpt, not a captured response:
{
"data": {
"id": 123,
"external_id": "integracion-venta-0001",
"source": "api",
"seller_id": null,
"created_by": {
"type": "api_key",
"id": 42,
"name": "Shopify",
"label": "API · Shopify",
"external_actor_id": null
},
"status": "draft",
"currency": "PEN",
"subtotal_net": "100.00",
"tax_total": "18.00",
"total": "118.00"
}
}Amounts are serialized as strings. The response also includes issue_date, customer, items, nullable seller_id, and created_by identifying the creator. The public response is independent from the internal application response.
Errors
401: invalid credentials. 403: access denied, insufficient scope or catalog-price mismatch. 409: external_id already exists for this API key (external_id_conflict). 422: invalid fields, invalid/inactive seller, seller from another company, or invalid external actor header. 429: rate limit exceeded.
Payments, advances, manual discounts, fiscal issuance and attachments are outside this initial contract. Do not send internal fields such as send_to_fiscal, payments or global_discount. Only documented fields are processed.