Skip to main content

External Orders API

The External Orders API allows authorised external systems — ERPs, order management platforms, custom scripts — to push orders directly into the GSM Middleware database in real time.

Overview

DetailValue
EndpointPOST /wp-json/gsm-middleware/v1/external/orders
AuthenticationBearer API key (per-site, managed in the admin)
Content-Typeapplication/json
Response on success201 Created

The inserted order flows through the standard rm_order / rm_lineitems / rm_address tables and is immediately available in the Orders viewer and picked up by the Business Central export cron.


Authentication

Generating an API Key

  1. In the WordPress admin, go to API Keys (the menu between API Connections and DB Environments).
  2. Click Generate New Key.
  3. Select the site the key should be authorised for.
  4. Enter a descriptive label (e.g. ERP Integration – Production).
  5. Click Generate Key.
  6. Copy the key immediately — it is shown only once and cannot be recovered.

Using the Key

Send the key in the Authorization header as a Bearer token:

POST /wp-json/gsm-middleware/v1/external/orders HTTP/1.1
Host: yoursite.com
Authorization: Bearer gsm_abc123def456...
Content-Type: application/json

Alternatively use the X-API-Key header:

X-API-Key: gsm_abc123def456...
caution

The key is tied to a specific site. You do not need to specify a site_id in the request body — it is determined from the key itself.


Request Body

All top-level fields listed below form the JSON body.

Top-level fields

FieldTypeRequiredDescription
order_numberstringUnique order identifier. Max 20 characters.
datestringOrder date. ISO-8601 date (YYYY-MM-DD) or full datetime accepted.
payment_methodenumOne of: stripe, nmi, klarna, credova, sezzle, paypal
transaction_idstringPayment gateway transaction ID. Max 20 characters.
contact_idintegerNavision contact ID. Defaults to 27111. Automatically set to payment-method defaults when not provided (Klarna → 27593, Sezzle → 37457, PayPal → 38407).
shipping_agent_codestringShipping carrier code, e.g. FEDEX, UPS. Default: FEDEX.
shipping_agent_servicestringShipping service, e.g. GROUND_HOME_DELIVERY. Default: GROUND_HOME_DELIVERY.
billing_addressobjectSee Address object.
shipping_addressobjectDefaults to billing_address when omitted. See Address object.
line_itemsarraySee Line item object. Min 1, max 500.

Address object

Used for both billing_address and shipping_address.

FieldTypeRequiredNotes
first_namestringMax 50 chars after truncation
last_namestringMax 50 chars
companystringMax 10 chars
address1stringMax 50 chars
address2stringMax 50 chars
citystringMax 30 chars
statestringTwo-letter abbreviation (e.g. TX). Max 30 chars
postal_codestringMax 20 chars
countrystringISO 3166-1 alpha-2 (e.g. US). Default: US
phonestringMax 30 chars
emailstringValid email. Max 58 chars. Used as the primary contact email for this order

Line item object

FieldTypeRequiredNotes
line_numberinteger1-based position within the order
skustringProduct SKU. Must exist in rm_items (except system SKUs)
quantityintegerMinimum 1
unit_pricenumberPrice per unit, excluding tax
coupon_codestringCoupon code applied to this line. Max 50 chars
discount_amountnumberDiscount amount (positive value). Default: 0
weightnumberWeight in pounds. Default: 0

System SKUs

The following SKUs are reserved and do not need to exist in rm_items:

SKUPurpose
COUPONCoupon / discount line
SHIPPINGShipping charge line
SALES TAXSales tax line
STORE CREDITStore credit applied

Example Request

curl -X POST "https://yoursite.com/wp-json/gsm-middleware/v1/external/orders" \
-H "Authorization: Bearer gsm_abc123def456..." \
-H "Content-Type: application/json" \
-d '{
"order_number": "ERP-78945",
"date": "2026-04-06",
"payment_method": "nmi",
"transaction_id": "TXN98765432",
"shipping_agent_code": "FEDEX",
"shipping_agent_service": "GROUND_HOME_DELIVERY",
"billing_address": {
"first_name": "Jane",
"last_name": "Smith",
"address1": "123 Main St",
"city": "Austin",
"state": "TX",
"postal_code": "78701",
"country": "US",
"phone": "512-555-0100",
"email": "[email protected]"
},
"line_items": [
{
"line_number": 1,
"sku": "PHX-1234-BLK",
"quantity": 2,
"unit_price": 49.99,
"weight": 1.5
},
{
"line_number": 2,
"sku": "SHIPPING",
"quantity": 1,
"unit_price": 12.95
}
]
}'

Example Response (Success)

HTTP/1.1 201 Created
Content-Type: application/json

{
"success": true,
"order_number": "ERP-78945",
"message": "Order ERP-78945 has been successfully ingested into the middleware."
}

Error Responses

HTTP StatusError CodeDescription
400rest_missing_callback_paramA required field is absent from the request body
400rest_invalid_paramA field value fails validation (wrong type, out of range, etc.)
400invalid_dateThe date field cannot be parsed as a valid date
401rest_forbiddenAPI key is missing, invalid, or revoked
422unknown_skusOne or more SKUs do not exist in rm_items. The response body includes an unknown_skus array
500db_errorA database write failed

Example 401

HTTP/1.1 401 Unauthorized

{
"code": "rest_forbidden",
"message": "Invalid or revoked API key.",
"data": { "status": 401 }
}

Example 422 — Unknown SKUs

HTTP/1.1 422 Unprocessable Entity

{
"code": "unknown_skus",
"message": "One or more SKUs do not exist in the inventory table (rm_items). Ensure all products are synced before submitting orders.",
"data": {
"status": 422,
"unknown_skus": ["WIDGET-999-RED", "GADGET-XL"]
}
}

SKU Validation

Before inserting any data, the endpoint verifies that every non-system SKU in line_items exists in the rm_items table. If any SKU is unknown, the entire request is rejected with HTTP 422 and an unknown_skus list.

To pre-check:

  • Ensure product inventory has been fully synced (run the inventory sync for the relevant site).
  • Use the Items viewer in the admin to search for a SKU before submitting.

Field Length Constraints

Fields are silently truncated to the following database column limits (inherited from Order_Repository::FIELD_LENGTHS):

FieldMax length
order_number20
transaction_id20
first_name50
last_name50
company10
address150
address250
city30
state30
postal_code20
country10
phone30
email58
coupon_code50
note

company is limited to 10 characters by the underlying database schema.


Key Management

Revoking a Key

  1. Go to WordPress Admin → API Keys.
  2. Click Revoke on the relevant row.
  3. Confirm the dialog. Revocation is immediate and permanent.
  4. Generate a new key if continued access is required.

Rotating a Key (Best Practice)

  1. Generate a replacement key.
  2. Update the external tool with the new key.
  3. Revoke the old key only after confirming the new key works.

Security Notes

  • API keys are stored as SHA-256 hashes in rm_api_keys; the plaintext is never persisted.
  • Keys are scoped to a single site and cannot write orders to any other site.
  • All endpoints require HTTPS in production.
  • There is no scope for reading data — the key grants write:orders only.
  • Rotate keys regularly and revoke any key that may have been exposed.