Morffeus Docs
ENSR morffeus.com

Proforma orders

A proforma order is a priced offer that comes before the order: a pro-forma invoice. It freezes the prices of a basket, reserves the stock, and gets a number and a bank payment reference. The customer pays it, your warehouse confirms what it can deliver, and you convert it into an order. A proforma that is not paid in time expires.

Lifecycle#

A proforma is made from a basket that checkout has priced, with its promotions and benefits applied: the order evaluation, named by order_processing_id. From there it moves through these statuses:

StatusWhat it means
draftCreated. Prices are frozen, stock is reserved, the amount is owed.
awaiting_paymentA card payment is in progress, for example waiting for 3-D Secure.
paidWhat was paid covers the total. Partial payments add up until it does.
confirmedThe warehouse confirmed every line in full.
partial_confirmedThe warehouse confirmed less on some lines. The proforma was repriced and the difference refunded.
convertedTurned into an order. Final.
canceledCancelled; captured payments were refunded and the stock released. Final.
expiredNot finished in time and cancelled automatically, without a message to the customer. Final.

A proforma lives 72 hours unless your app is set otherwise; expires_at says when. Its lines, prices, customer and delivery cannot be changed: to change them, cancel the proforma and make a new one. Only its tags, comment and alerts can be edited.

The proforma in lists#

What GET /proforma_orders returns for each proforma. Amounts are numbers in the proforma's currency.

FieldDescription
idintegerOur id of the proforma.
proforma_numberstringIts number, such as P-2026-7KQ2XD. Unique within your app, and not sequential.
proforma_status_id, proforma_status_name, proforma_status_color, proforma_status_finalThe status.
processing_datedatetimeWhen the prices were frozen.
expires_at, expires_in_secondsWhen the proforma expires, and how long is left.
customer_id, customer_email, customer_first_name, customer_last_nameThe customer. anonymous is true for a guest.
sales, discount, net_sales, vat, end_salesnumberGross, discount, net, VAT and the total to pay.
paid, debtnumberWhat has been paid and what is still owed.
market_id, currency_id, sales_channel_id, sales_channelWhere the proforma was made.
warehouse_confirmed_at, converted_at, order_idnullableWhen the warehouse confirmed it, when it was converted, and the order it became.
canceled, canceled_at, cancel_reasonWhether, when and why it was cancelled.
tags, alerts_count, open_alerts_countYour tags, and how many alerts it has and how many are unresolved. An alert records something that needs a person, such as a refund that failed.
inserted_at, updated_atdatetime · ISO 8601, UTCWhen it was created and last changed. Sent without an offset.

List proformas#

GET /api/v2/proforma_orders

Your app's proformas, a page at a time. Use the filters to find what needs action: proformas about to expire, paid ones waiting for the warehouse, ones with open alerts.

Query parameters

ParameterDescription
statusstringOne status name, or several separated by commas: status=paid,confirmed.
expiring_withinintegerOpen proformas that expire within this many hours.
has_alertsbooleantrue: at least one unresolved alert. false: none.
anonymousbooleanOnly guest proformas, or only customers' ones.
customer_idintegerOne customer's proformas.
date_from, date_todate or datetimeBy processing_date, inclusive. A date alone covers the whole day.
orderBystringexpires_at, end_sales, paid, debt, customer_name, processing_date, inserted_at, updated_at, proforma_number or status, optionally followed by asc or desc.
offset, limitinteger · default 0, 30Paging. See Pagination & filtering.

A filter value we cannot use is ignored rather than refused.

Returns

data: a list of proformas. GET /proforma_orders/stats returns the counts behind a dashboard instead: open proformas and their sum, those awaiting payment, those expiring within 24 hours, paid but not converted, converted in the last 7 days, and the conversion rate between date_from and date_to (default: the last 30 days).

GET/proforma_orders
curl -G "https://api.morffeus.com/api/v2/proforma_orders" \
  -H "Authorization: Bearer $MORFFEUS_TOKEN" \
  -d "status=paid" -d "orderBy=expires_at"
Response200 OK
{
  "data": [
    {
      "id": 2207,
      "proforma_number": "P-2026-7KQ2XD",
      "proforma_status_name": "paid",
      "proforma_status_final": false,
      "processing_date": "2026-10-01T09:12:40",
      "expires_at": "2026-10-04T09:12:40",
      "expires_in_seconds": 203400,
      "customer_id": 5521,
      "end_sales": 5490.0,
      "paid": 5490.0,
      "debt": 0.0,
      "order_id": null,
      "open_alerts_count": 0,
      …
    }
  ]
}

Read one proforma#

GET /api/v2/proforma_orders/{id}

One proforma in full, shaped like an order so the same code can read both.

Returns

data: the proforma's fields and amounts, plus:

  • items and fee_items: the lines, each with the quantity requested and, after the warehouse step, the quantity confirmed.
  • payments: each payment with amount, payment_method, payment_status, payment_date and finalized.
  • payment_instructions: what a bank transfer needs: the beneficiary and bank account your app has set up, the amount still owed, and the payment reference (model 97).
  • tax_brackets, transaction_reference, delivery_area, and alerts with their resolution.
  • converted_order_number once it is converted.

A proforma that is not in your app returns 404 with an empty body.

Response200 OK
{
  "data": {
    "id": 2207,
    "proforma_number": "P-2026-7KQ2XD",
    "proforma_status": "paid",
    "order_processing_id": 88123,
    "end_sales": 5490.0,
    "paid": 5490.0,
    "payments": [
      {
        "amount": 5490.0,
        "payment_method": "Bank transfer",
        "payment_status": "Captured",
        "finalized": true,
        …
      }
    ],
    "payment_instructions": { … },
    "alerts": [],
    …
  }
}

Create a proforma#

POST /api/v2/proforma_orders

Turns an order evaluation into a proforma. The prices, promotions and benefits of the evaluation are frozen, the stock is reserved, and the evaluation is used up: it cannot make a second proforma or an order.

Body parameters

ParameterDescription
order_processing_idrequiredintegerThe order evaluation, from checkout. Not wrapped in an object.

Returns

201 and the proforma in data, in the shape of Read one proforma.

Errors

StatusWhen
422The evaluation does not exist or was used: API.OrderProcessings.StaleEvaluation. It belongs to another app: API.ProformaOrders.WrongApp. A product has no VAT: API.Products.MissingVat. A guest's delivery data is incomplete: API.Orders.GuestDeliveryDataIncomplete. A pickup point is missing or unavailable: API.ShipmentMethods.PickupPointRequired, API.ShipmentMethods.PickupPointUnavailable. There is not enough stock: Insufficient stock for product: ….
POST/proforma_orders
curl -X POST "https://api.morffeus.com/api/v2/proforma_orders" \
  -H "Authorization: Bearer $MORFFEUS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{ "order_processing_id": 88123 }'
Error422 Unprocessable Entity
{
  "errors": {
    "OrderProcessings": ["API.OrderProcessings.StaleEvaluation"]
  }
}

Pay, confirm, convert#

POST /api/v2/proforma_orders/{id}/convert

The three steps that move a proforma forward. Each returns the proforma, or for convert the order, and fails with API.ProformaOrders.AlreadyFinal on a proforma that is converted, cancelled or expired.

CallDescription
POST /proforma_orders/{id}/payRecords a payment. Body: {"payment": {"payment_method_id": …, "amount": …}}. An amount of 0 or less pays the whole remaining debt. Use it for payments that settle at once, such as cash or a bank transfer you received; card payments that need the customer's browser go through the storefront checkout. When the payments cover the total, the status becomes paid.
POST /proforma_orders/{id}/warehouse_confirmConfirms what the warehouse can deliver. Body: {"adjustments": [{"proforma_order_item_id": …, "qty_confirmed": …}]}. Lines you leave out are confirmed in full. When a line is reduced, the proforma is repriced with the promotions it had when it was made, the stock no longer needed is released, the overpaid difference is refunded, and the status becomes partial_confirmed; otherwise confirmed.
POST /proforma_orders/{id}/convertCreates the order: its lines carry the confirmed quantities, the payments move to the order, and the customer's earned benefits are booked. The stock stays reserved as it was. Calling it again returns the same order. The response is the order's record in data.

Errors

StatusWhen
404No such proforma in your app. The body is empty.
422The proforma is final: API.ProformaOrders.AlreadyFinal. The payment method is not supported: API.PaymentMethod.NotSupported. The payment is larger than the debt, or the debt is already paid: Payment amount exceeds order debt, Order debt is already paid. A cash payment needs an amount: API.Payments.CashAmountRequired.

A refund that fails during the warehouse step does not fail the call: it is added to the proforma's alerts for someone to follow up.

POST/proforma_orders/{id}/warehouse_confirm
curl -X POST "https://api.morffeus.com/api/v2/proforma_orders/2207/warehouse_confirm" \
  -H "Authorization: Bearer $MORFFEUS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "adjustments": [
      { "proforma_order_item_id": 9911, "qty_confirmed": 1 }
    ]
  }'
Error422 Unprocessable Entity
{
  "errors": {
    "ProformaOrders": ["API.ProformaOrders.AlreadyFinal"]
  }
}

Tag, comment or cancel#

DELETE /api/v2/proforma_orders/{id}
CallDescription
PUT /proforma_orders/{id}, PATCHChanges the only editable parts. Body: tags (a list of strings, which replaces the old one), comment, and alerts to resolve, each as {"index": …, "resolved": true}. Tags and comment cannot change on a final proforma; alerts can always be resolved. Returns the proforma.
DELETE /proforma_orders/{id}Cancels the proforma: captured payments are refunded, the stock and any held benefits are released, and the status becomes canceled. Optional body: reason. Returns 204 with no body.

Errors

StatusWhen
404No such proforma in your app.
422The proforma is final: API.ProformaOrders.AlreadyFinal. Tags or comment are not valid: API.ProformaOrders.InvalidTags, API.ProformaOrders.InvalidComment. An alert to resolve does not exist: API.ProformaOrders.AlertNotFound, and nothing is written.
DELETE/proforma_orders/{id}
curl -X DELETE "https://api.morffeus.com/api/v2/proforma_orders/2207" \
  -H "Authorization: Bearer $MORFFEUS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{ "reason": "Customer changed the order" }'
Last updated 1 October 2026 · API v2 Something wrong on this page?