Create an Order

This endpoint lets you create an order manually from your system. Use it when you need to capture sales that don't come from a connected channel, for example:

  • Phone sales — a customer calls and you take their order on the spot.
  • Counter sales without a connected POS.
  • Custom integrations with ERPs, proprietary marketplaces, or legacy systems.
  • Historical migrations from another platform (use orderCreated to preserve the original date).

If you need to load several orders at once, use POST /orders/import instead of calling this endpoint in a loop.

Orders from connected channels

If the order already exists on a connected channel (Shopify, Amazon, MercadoLibre…), don't create it manually. Fenicia syncs it automatically. Creating duplicate orders causes inventory inconsistencies.

Endpoint

Creates a new order in the authenticated tenant. The request body must be wrapped in the order property.

orderobjectrequired

Required envelope. The entire request body goes nested inside this property — the root schema is strict and doesn't accept properties outside of order.

{
  "order": {
    "externalId": "FEN-10042",
    "handle": "fen-10042",
    "locationId": "65f2a1b2c4d5e6f7a8b9c0aa",
    "origin": "manual",
    "channelId": "65f2a1b2c4d5e6f7a8b9c0bb",
    "orderStatus": "pending",
    "paymentStatus": "paid",
    "currency": "MXN",
    "totalAmount": 1986.26,
    "orderCreated": "2026-04-11T14:23:11.000Z",
    "items": [
      { "sku": "CAM-ROJO-M", "title": "Camisa Roja Talla M", "quantity": 2, "totalAmount": 998.00 },
      { "sku": "PAN-AZUL-32", "title": "Pantalón Azul 32", "quantity": 1, "totalAmount": 799.50 }
    ],
    "customerInfo": { "name": "María González" },
    "shippingAddress": {
      "street": "Av. Reforma 123",
      "city": "Ciudad de México",
      "state": "CDMX",
      "zipCode": "06600",
      "country": "MX"
    },
    "notes": "Cliente pidió empaque de regalo"
  }
}
201
{
  "data": {
    "_id": "65f3a1b2c4d5e6f7a8b9c0d1",
    "externalId": "FEN-10042",
    "handle": "fen-10042",
    "orderStatus": "pending",
    "paymentStatus": "paid",
    "currency": "MXN",
    "totalAmount": 1986.26,
    "items": [
      { "sku": "CAM-ROJO-M", "title": "Camisa Roja Talla M", "quantity": 2, "totalAmount": 998.00 },
      { "sku": "PAN-AZUL-32", "title": "Pantalón Azul 32", "quantity": 1, "totalAmount": 799.50 }
    ],
    "customerInfo": { "name": "María González" },
    "orderCreated": "2026-04-11T14:23:11.000Z",
    "_links": {
      "self": "/orders/65f3a1b2c4d5e6f7a8b9c0d1",
      "accept": "/orders/65f3a1b2c4d5e6f7a8b9c0d1/accept",
      "reject": "/orders/65f3a1b2c4d5e6f7a8b9c0d1/reject"
    }
  }
}
400
{ "error": { "code": "validation:invalid_input", "message": "order object is required" } }

Required permission: orders:create

The order envelope is required

The request body is not the order object directly — it must be nested inside an order property: { "order": { ... } }. The schema validator is strict (.strict()): any unknown property at the root of the JSON (outside of order) causes the request to be rejected with validation:invalid_input.

Request body — order fields

Required fields (10)

These 10 fields must always be included inside order. If any is missing, the request is rejected.

FieldTypeDescription
externalIdstringThe order's identifier in your source system.
handlestringHuman-readable handle or folio for the order.
locationIdstringID of the location (warehouse/store) fulfilling the order.
originstringSales origin (for example manual).
channelIdstringID of the sales channel the order belongs to.
orderStatusstringInitial order status. Must be a valid state from the state machine.
paymentStatusstringPayment status at the time the order is created.
currencystringISO 4217 currency code, exactly 3 letters (e.g. MXN, USD).
totalAmountnumberTotal order amount.
orderCreatedstring (ISO 8601)Original creation date of the order. Useful when migrating historical orders, where it doesn't match the request date.

Optional fields

FieldTypeDescription
itemsarrayOrder line items. See exact shape below.
customerInfoobjectCustomer data. See exact shape below.
customerIdstringID of an existing customer in your CRM.
notesstringInternal notes visible only to your team.
tagsarrayTags to classify the order.
discountsarrayDiscounts applied to the order.
subtotalnumberSubtotal before taxes and shipping.
taxnumberTotal order tax.
shippingnumberShipping cost charged to the customer.
paymentobjectPayment information (method, reference, etc.).
paymentSummaryobjectPayment summary.
deliveryInfoobjectDelivery information (method, carrier).
billingAddressobjectBilling address.
shippingAddressobjectShipping address.
metadataobjectArbitrary key-value pairs for your integration.
isCounterSalebooleanIndicates whether this is a counter sale.

customerInfo and additional content

customerInfo accepts additional properties beyond the documented ones (it doesn't reject them), but only name is confirmed to be validated. Don't assume other fields inside customerInfo are validated or used.

Shape of items[]

Each order line has this shape:

FieldTypeDescription
skustringProduct SKU.
titlestringLine item name/title.
quantitynumberQuantity sold.
totalAmountnumberTotal amount for the line (not unit price).

Shape of customerInfo

FieldTypeRequiredDescription
namestringYes (if you send customerInfo)Customer name.

Full example

curl -X POST https://api.fenicia.io/orders \
  -H "Authorization: Bearer fkapi_your_api_key" \
  -H "Content-Type: application/json" \
  -d '{
    "order": {
      "externalId": "FEN-10042",
      "handle": "fen-10042",
      "locationId": "65f2a1b2c4d5e6f7a8b9c0aa",
      "origin": "manual",
      "channelId": "65f2a1b2c4d5e6f7a8b9c0bb",
      "orderStatus": "pending",
      "paymentStatus": "paid",
      "currency": "MXN",
      "totalAmount": 1986.26,
      "orderCreated": "2026-04-11T14:23:11.000Z",
      "items": [
        { "sku": "CAM-ROJO-M", "title": "Camisa Roja Talla M", "quantity": 2, "totalAmount": 998.00 },
        { "sku": "PAN-AZUL-32", "title": "Pantalón Azul 32", "quantity": 1, "totalAmount": 799.50 }
      ],
      "customerInfo": { "name": "María González" },
      "shippingAddress": {
        "street": "Av. Reforma 123",
        "city": "Ciudad de México",
        "state": "CDMX",
        "zipCode": "06600",
        "country": "MX"
      },
      "notes": "Cliente pidió empaque de regalo"
    }
  }'

Example response

{
  "data": {
    "_id": "65f3a1b2c4d5e6f7a8b9c0d1",
    "externalId": "FEN-10042",
    "handle": "fen-10042",
    "orderStatus": "pending",
    "paymentStatus": "paid",
    "currency": "MXN",
    "totalAmount": 1986.26,
    "items": [
      { "sku": "CAM-ROJO-M", "title": "Camisa Roja Talla M", "quantity": 2, "totalAmount": 998.00 },
      { "sku": "PAN-AZUL-32", "title": "Pantalón Azul 32", "quantity": 1, "totalAmount": 799.50 }
    ],
    "customerInfo": { "name": "María González" },
    "orderCreated": "2026-04-11T14:23:11.000Z",
    "_links": {
      "self": "/orders/65f3a1b2c4d5e6f7a8b9c0d1",
      "accept": "/orders/65f3a1b2c4d5e6f7a8b9c0d1/accept",
      "reject": "/orders/65f3a1b2c4d5e6f7a8b9c0d1/reject"
    }
  }
}

Tip

Save the returned _id. You'll need it to retrieve, update, or transition the order in subsequent calls.

Bulk import orders

To load several orders in a single call (migrations, backfills), use the import endpoint.

Imports a batch of orders into the authenticated tenant

ordersarrayrequired

Array of order objects. Each one requires, at minimum, locationId.

{
  "orders": [
    { "externalId": "LEGACY-001", "locationId": "65f2a1b2c4d5e6f7a8b9c0aa", "...": "..." },
    { "externalId": "LEGACY-002", "locationId": "65f2a1b2c4d5e6f7a8b9c0aa", "...": "..." }
  ]
}
200
{ "data": { "imported": 2 } }
400
{ "error": { "code": "validation:invalid_input", "message": "orders must be an array" } }

Required permission: orders:import

Per-order validation

Validation of each order in the batch is applied by the domain library, not by a per-item transport schema documented here. Test with a small batch on a development tenant first before running a mass import.

Errors

CodeStatusDescription
validation:invalid_input400The body doesn't satisfy the schema — a required field is missing, a type doesn't match, or there's an unknown property at the root (outside of order).
auth:invalid_token401The API key is invalid or has been revoked.
auth:permission_denied403The API key doesn't have the orders:create scope (or orders:import for the batch).
internal:server_error500Internal error while processing the order.

See the full error catalog for the rest of the domain's codes.

Next steps