Order Records

7 views Markdown

The six endpoints that read, move and remove orders already placed.

Overview

An order is the record of a purchase and a service is the thing born from it. The two live apart: removing an order does not kill the service, and closing a service does not change the order.

The detail endpoint shows that split plainly. The lines are the order's own and do not move, while the service list comes back live on every read, its states taken from the service record.

Tax and discounts are kept the same way: the rates from order time are frozen inside the record. Working them out again at today's setting misreads a past bill.

Reference

Listing the Orders

get/api/v1/admin/orders
Orders/GetOrders admin

Returns the orders page by page, with the client and invoice summary.

Query 9
pageintWhich page.
limitintRecords per page. A value out of range counts as 25.
searchstringSearches the order number, the client details and the address.
statusstringThe order state filter: waiting, in process, active or cancelled.
groupstringThe product group filter. It searches inside the order lines.
paymentstringThe invoice state filter: complete, incomplete or unknown.
client_idintThe client id.
numberintThe order number. It takes a partial match.
ipstringThe address the order came from. It takes a partial match.
Response fields data[] — 13 + meta — 4
idintThe order id.
order_numberstringThe order number people see.
statusstringWhere the order stands.
amountfloatThe order amount.
currency_idintThe currency number of the amount.
payment_methodstringThe payment module. It comes empty when none was picked.
invoice_idintThe invoice tied to it. Zero means there is none.
invoice_statusstringWhere the invoice stands.
item_countintHow many lines the order holds.
has_active_moduleboolWhether it holds a service tied to a provider.
ipstringThe address the order came from.
created_atstringWhen it was made.
clientobjectThe client summary. It carries the id, name, company and e-mail.
totalintHow many orders there are. It comes back under meta.
pageintThe page you are on.
limitintThe page size.
next_pageintThe next page.
Errors 1
insufficient_scope403The key lacks the required scope.
Request
curl 'https://panel.example.com/api/v1/admin/orders?status=waiting&limit=50' \
  -H "Authorization: Bearer $API_KEY"
const res = await fetch('https://panel.example.com/api/v1/admin/orders?status=waiting', {
  headers: { Authorization: `Bearer ${apiKey}` },
});

const { data, meta } = await res.json();
const unpaid = data.filter((o) => o.invoice_status === 'unpaid');
$ch = curl_init('https://panel.example.com/api/v1/admin/orders?status=waiting');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER     => ['Authorization: Bearer ' . $apiKey],
]);

$body = json_decode(curl_exec($ch), true);
curl_close($ch);
// A listed order HAS the amount and NOT the lines: lines and services come on the detail endpoint alone.
$rows = Api::Orders()->GetOrders([], ['status' => 'waiting'])['data'];
foreach ($rows as $o) $detail[$o['id']] = Api::Orders()->GetOrder(['id' => $o['id']])['data'];

Reading an Order in Full

get/api/v1/admin/orders/{id}
Orders/GetOrder admin

Returns an order's tax, discounts, lines and the services it brought into being.

Response fields data — 17
idintThe order id.
order_numberstringThe order number people see.
statusstringWhere the order stands.
amountfloatThe order amount.
currency_idintThe currency number.
payment_methodstringThe payment module.
tax_typestringWhether tax sits inside the price or beside it.
taxesobjectThe tax snapshot. It keeps the rate and amount as they were at order time.
typestringWhether tax sits inside the price or beside it.
ratefloatThe rate applied.
system_ratefloatThe rate the system set.
amountfloatThe tax amount.
additionalobjectThe additional taxes and their total.
exemptionintWhether an exemption was applied.
discountsobjectThe discount snapshot.
resellerobjectThe dealer discount: its total, its lines and its groups.
couponobjectThe coupon discount: its total and its lines.
totalfloatThe discount in all.
detailsobjectThe extra information from order time.
subtotalfloatThe subtotal.
display_subtotalfloatThe subtotal shown.
taxable_subtotalfloatThe subtotal taxed.
billing_profile_idintThe billing address profile.
send_notificationintWhether the client was told.
generate_invoiceintWhether an invoice was raised.
invoice_statusstringThe invoice state.
promo_codesarrayThe coupon ids applied.
created_bystringWho opened the order: a member of staff or the client.
admin_idintThe id of the staff who opened it.
notesstringThe order note.
ipstringThe address the order came from.
affiliate_idintThe partner credited. Zero means none.
created_atstringWhen it was made.
clientobjectThe client summary. It carries the id, name, company, e-mail and language.
invoiceobjectThe invoice tied to it. It carries the id, number, state and total.
itemsarrayThe raw order lines.
typestringThe line kind: a product or a domain.
product_idintThe product id.
product_namestringThe product name.
product_typestringThe product type.
domainstringThe domain name.
tldstringThe extension.
sldstringThe body of the name.
billing_cyclestringThe billing cycle.
periodintThe term. On a domain line it is the number of years.
pricefloatThe unit price.
quantityintHow many.
allow_qtyintWhether more than one is allowed.
actionstringThe domain action: a registration or a transfer.
requirementsarrayThe answers the client gave. Each carries the question id, name, type, value and module mapping.
addonsarrayThe add-ons picked.
servicesint[]The service ids born from this line.
invoice_item_idintThe invoice line tied to it.
servicesarrayThe services born from the order. Their states are read live from the service record.
idintThe service id.
existsboolWhether the service still stands. False means the line never became one or the service was removed.
namestringThe service name.
typestringThe service type.
product_idintThe product id.
statusstringThe service's live state.
amountfloatThe service amount.
total_amountfloatThe total with the add-ons.
currency_idintThe currency number.
periodstringThe period unit.
period_timeintThe period multiplier.
cyclestringThe billing cycle.
modulestringThe provider module.
has_requirementsboolWhether it carries answers.
optionsobjectThe service options.
addonsarrayThe service's add-ons. Each carries the choice, quantity, state and amount.
Errors 2
not_found404No such order.
insufficient_scope403The key lacks the required scope.
Request
curl 'https://panel.example.com/api/v1/admin/orders/92' \
  -H "Authorization: Bearer $API_KEY"
const res = await fetch(`https://panel.example.com/api/v1/admin/orders/${id}`, {
  headers: { Authorization: `Bearer ${apiKey}` },
});

const { data } = await res.json();
const live = data.services.filter((s) => s.exists);
$ch = curl_init('https://panel.example.com/api/v1/admin/orders/' . $id);
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER     => ['Authorization: Bearer ' . $apiKey],
]);

$body = json_decode(curl_exec($ch), true);
curl_close($ch);
// The tax and discount are a snapshot of ORDER TIME; do not work them out again at today's rate.
$o = Api::Orders()->GetOrder(['id' => $id])['data'];
$rateThen = $o['taxes']['rate'] ?? 0;

Updating the Order Envelope

patch/api/v1/admin/orders/{id}
Orders/UpdateOrder admin two fields

Changes an order's note and which partner is credited.

Body 2
notesstringThe order note.
affiliate_idintThe partner credited. Zero takes the credit away.
Response fields data — 17
dataobjectThe order as it now stands. Same shape as the read endpoint.
Errors 2
not_found404No such order.
insufficient_scope403The key lacks the required scope.
Request
curl -X PATCH 'https://panel.example.com/api/v1/admin/orders/92' \
  -H "Authorization: Bearer $API_KEY" \
  -H 'Content-Type: application/json' \
  -d '{"notes":"Expedite provisioning","affiliate_id":5}'
const res = await fetch(`https://panel.example.com/api/v1/admin/orders/${id}`, {
  method: 'PATCH',
  headers: {
    Authorization: `Bearer ${apiKey}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({ notes: 'Expedite provisioning' }),
});

const body = await res.json();
$ch = curl_init('https://panel.example.com/api/v1/admin/orders/' . $id);
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_CUSTOMREQUEST  => 'PATCH',
    CURLOPT_HTTPHEADER     => [
        'Authorization: Bearer ' . $apiKey,
        'Content-Type: application/json',
    ],
    CURLOPT_POSTFIELDS => json_encode(['notes' => $note]),
]);

$body = json_decode(curl_exec($ch), true);
curl_close($ch);
// The note and the partner change alone; lines, prices and the client CANNOT be edited here.
Api::Orders()->UpdateOrder(['id' => $id, 'notes' => $note, 'affiliate_id' => $aff]);

Changing the Order State

put/api/v1/admin/orders/{id}/status
Orders/UpdateOrderStatus admin it reaches the services

Changes an order's state and carries it through to its services.

Body 2
statusstringreqThe new state: waiting, in process, active or cancelled.
apply_on_moduleboolWhether the change reaches the provider too. It starts real work on the server.
Response fields data — 5
idintThe order id.
statusstringThe new state.
old_statusstringThe state before.
services_updatedboolWhether it reached the services. A move back to waiting does not reach them.
applied_on_moduleboolWhether it was applied at the provider.
Errors 5
not_found404No such order.
invalid_status422The state is none of the four values.
blocked_by_gate422A hook refused the change.
status_change_failed500The state could not be changed.
insufficient_scope403The key lacks the required scope.
Request
curl -X PUT 'https://panel.example.com/api/v1/admin/orders/92/status' \
  -H "Authorization: Bearer $API_KEY" \
  -H 'Content-Type: application/json' \
  -d '{"status":"active","apply_on_module":false}'
const res = await fetch(`https://panel.example.com/api/v1/admin/orders/${id}/status`, {
  method: 'PUT',
  headers: {
    Authorization: `Bearer ${apiKey}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({ status: 'active', apply_on_module: false }),
});

const { data } = await res.json();
if (data.services_updated) refreshServices();
$ch = curl_init('https://panel.example.com/api/v1/admin/orders/' . $id . '/status');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_CUSTOMREQUEST  => 'PUT',
    CURLOPT_HTTPHEADER     => [
        'Authorization: Bearer ' . $apiKey,
        'Content-Type: application/json',
    ],
    CURLOPT_POSTFIELDS => json_encode(['status' => 'active', 'apply_on_module' => false]),
]);

$body = json_decode(curl_exec($ch), true);
curl_close($ch);
// apply_on_module starts real work AT THE PROVIDER: active builds and cancelled shuts down. Leave it false first.
Api::Orders()->UpdateOrderStatus([
    'id' => $id, 'status' => 'active', 'apply_on_module' => false,
]);

Removing an Order

delete/api/v1/admin/orders/{id}
Orders/DeleteOrder admin

Removes the order record while the services it made stay where they are.

Response fields data — 2
deletedboolWhether the delete ran.
idintThe id of the order removed.
Errors 3
not_found404No such order.
blocked_by_gate422A hook refused the delete.
insufficient_scope403The key lacks the required scope.
Request
curl -X DELETE 'https://panel.example.com/api/v1/admin/orders/92' \
  -H "Authorization: Bearer $API_KEY"
const res = await fetch(`https://panel.example.com/api/v1/admin/orders/${id}`, {
  method: 'DELETE',
  headers: { Authorization: `Bearer ${apiKey}` },
});

const body = await res.json();
$ch = curl_init('https://panel.example.com/api/v1/admin/orders/' . $id);
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_CUSTOMREQUEST  => 'DELETE',
    CURLOPT_HTTPHEADER     => ['Authorization: Bearer ' . $apiKey],
]);

$body = json_decode(curl_exec($ch), true);
curl_close($ch);
// The services ARE NOT removed: the order goes, they stay, and which order made them is lost.
$o = Api::Orders()->GetOrder(['id' => $id])['data'];
foreach ($o['services'] as $s) if ($s['exists']) $orphans[] = $s['id'];

Api::Orders()->DeleteOrder(['id' => $id]);

Removing One of an Order's Services

delete/api/v1/admin/orders/{id}/services/{sid}
Orders/DeleteOrderService admin

Removes a service the order brought into being.

Body 1
apply_on_moduleboolWhether it is cancelled at the provider too.
Response fields data — 4
deletedboolWhether the delete ran.
service_idintThe id of the service removed.
order_idintThe order id.
applied_on_moduleboolWhether it was applied at the provider.
Errors 2
not_found404No such order or service, or the service belongs to another order.
insufficient_scope403The key lacks the required scope.
Request
curl -X DELETE 'https://panel.example.com/api/v1/admin/orders/92/services/561' \
  -H "Authorization: Bearer $API_KEY" \
  -H 'Content-Type: application/json' \
  -d '{"apply_on_module":false}'
const res = await fetch(`https://panel.example.com/api/v1/admin/orders/${id}/services/${sid}`, {
  method: 'DELETE',
  headers: {
    Authorization: `Bearer ${apiKey}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({ apply_on_module: false }),
});

const body = await res.json();
$ch = curl_init('https://panel.example.com/api/v1/admin/orders/' . $id . '/services/' . $sid);
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_CUSTOMREQUEST  => 'DELETE',
    CURLOPT_HTTPHEADER     => [
        'Authorization: Bearer ' . $apiKey,
        'Content-Type: application/json',
    ],
    CURLOPT_POSTFIELDS => json_encode(['apply_on_module' => false]),
]);

$body = json_decode(curl_exec($ch), true);
curl_close($ch);
// A service belonging to another order gives 404; take the id from the service list in the detail.
$o = Api::Orders()->GetOrder(['id' => $id])['data'];
$mine = array_column($o['services'], 'id');
if (in_array($sid, $mine, true))
    Api::Orders()->DeleteOrderService(['id' => $id, 'sid' => $sid, 'apply_on_module' => false]);

Pitfalls

Removing an order does not remove the service

The order record goes and the services it made stay where they are, still billing. The one link left breaks as well: which order made a service can no longer be traced. Remove the services first when you mean to undo a purchase entirely.

Applying at the provider starts real work

Turning on the provider option during a state change starts real work on the server: active builds and cancelled shuts an account down. Leaving it on during a bulk correction reaches client servers all at once. Try it off first and look at the result.

Tax and discount are a frozen snapshot

The tax rate, the additional taxes and the coupon discount in the detail were worked out at order time and stand that way in the record. Two figures disagree when today's tax setting has moved, and that is historical accuracy rather than a fault. Use the record's own rate in a report.

The envelope update does not reprice an order

The update endpoint writes the note and the partner credit alone. Lines, quantities, prices, cycles and the client cannot be changed here. The way to handle an order built wrong is to cancel it and build another rather than to edit it.

A service is removed from its own order alone

The service removal endpoint checks that the service truly belongs to that order, and one from another order gets 404. That gate stops a wrong id from removing the wrong service. Take the id from the list in the detail.

The listing holds no lines and the detail does

The listing gives a summary for each order: the amount, how many lines and the client. The lines themselves, the answers to the questions and the services born come on the detail endpoint. Showing lines in a listing screen means one more call per row.

Was this helpful?

Thanks for your feedback!

Still Need Help?

Our support team is here around the clock for anything you can't find above.