How to place orders via API: new subscriptions, changes and cancellations
Introduction
Every order you place through the AppXite Platform API, whether it buys a new Subscription or changes an existing one, goes through the same two calls: Create an order (Simplified) saves the order and returns the operations you can run on it, and Execute an order (Simplified) runs the operation to submit it for provisioning. Only the request body changes from one order type to another. This article explains the shared flow and then walks through each order type with a working example for a Microsoft Offer.
Before you start, have the following ready. Retrieving them is out of scope for this article:
- Your platform URL (sent in the
X-Refererheader). - An
Ocp-Apim-Subscription-Keyfor the Orders product and a valid Bearer token. - The Customer organization ID, retrieved with the List all customers API.
- The
OfferIdof the product, retrieved with the catalog APIs. - The offer's form fields (
FormDatakeys and allowed values), retrieved with the Retrieve a pricing plan for an offer API. - For changes to an existing subscription: the
SubscriptionIdof that subscription, retrieved with the subscription APIs.
In this article
- How the order flow works
- Place a new order
- Change the quantity of a subscription
- Troubleshooting
- Limitations
- Related content
How the order flow works
An order is created first and submitted second. The first call validates the payload and stores the order in Start status; nothing is provisioned yet. The second call runs the operation you choose from the first response, and PlaceOrder moves the order to ProvisionStarted.
-
Create the order: send the order content. The response returns the order
Idand a list of availableOperations(for example CreateQuote, ScheduleOrder, SaveForLater, PlaceOrder). -
Execute the order: send the order
Id, the same order content inOrderCommand, and the PlaceOrder operation object from step 1.
| Create an order (Simplified) | Execute an order (Simplified) | |
|---|---|---|
| Method and URL | POST https://api.appxite.com/orders/ |
POST https://api.appxite.com/orders/operation |
| Platform roles | Distributor Admin, Seller Admin, Seller Agent Admin, Account Manager, Customer, Customer Admin | Seller Admin, Account Manager |
| Order status after the call | Start | ProvisionStarted |
The order type is set by the array you send in the request body. The rest of the flow is the same for every type:
| Order type | Array in the request body | Section |
|---|---|---|
| New subscription | OrderedOffers |
Place a new order |
| Quantity change |
SubscriptionChanges with ChangeType: SubscriptionChange
|
Change the quantity of a subscription |
All calls use the same request headers:
| Header | Required | Value |
|---|---|---|
X-Referer |
Yes | Your platform URL |
Ocp-Apim-Subscription-Key |
Yes | Subscription key for the Orders product |
Authorization |
Yes | Bearer <token> |
Api-Version |
Yes | 3.1 |
X-On-Behalf-Of |
Yes | Customer organization ID (GUID) |
Environment |
Yes for Create, optional for Execute |
Sandbox or Production. If omitted, the request goes to Sandbox. |
Content-Type |
Yes | application/json |
X-ApiUser |
No | Name shown as Created by in the order list |
Accept-Language |
No | Locale for translated responses, for example en-US
|
Environment header are routed to Sandbox. Set Environment: Production explicitly for live orders.Place a new order
Use this flow to buy a new subscription. The order content goes in the OrderedOffers array.
Step 1: Create the order
A successful call returns HTTP 200 with the order Id, the order in Start status, and the operations you can run next.
Request body fields
| Field | Required | Description |
|---|---|---|
OrderName |
Yes | Display name of the order. |
OrderedOffers[].OfferId |
Yes | ID of the Offer, from the catalog APIs. |
OrderedOffers[].OrderItemId |
Yes | A UUID you generate for each order item. |
OrderedOffers[].Quantity |
Yes | Quantity ordered. Keep it equal to the quantity key in FormData (for example quantityKey or numberKey). |
OrderedOffers[].Name |
Yes | Display name of the order item. |
OrderedOffers[].CustomerMarkup |
No | Markup for the Customer. Defaults to 0.0. |
OrderedOffers[].FormData |
Yes | The offer's form values. Keys and allowed values come from the Retrieve a pricing plan for an offer API and differ per offer. |
Example request (Microsoft Intune Device, 1 license, annual term, monthly billing):
curl --location 'https://api.appxite.com/orders/' \
--header 'X-Referer: <your platform URL>' \
--header 'Ocp-Apim-Subscription-Key: <subscription key>' \
--header 'Authorization: Bearer <token>' \
--header 'Api-Version: 3.1' \
--header 'Environment: Sandbox' \
--header 'X-ApiUser: Postman-API' \
--header 'X-On-Behalf-Of: 9819CF3C-FBD9-4A5C-92E7-3EF5922C36CE' \
--header 'Content-Type: application/json' \
--data '{
"OrderName": "Microsoft Intune Device ordered",
"OrderedOffers": [
{
"OfferId": "B359AEAC-BF3B-4F19-B4BF-86B4766CF222",
"OrderItemId": "789dc84f-87a9-4b78-8123-0910c11cab5a",
"Quantity": 1,
"CustomerMarkup": 0,
"Name": "Microsoft Intune Device",
"FormData": {
"quantityKey": 1,
"termDuration": "P1Y",
"billingCycle": "Monthly",
"customTermEndDates": "None",
"transitionEligibilities": "none",
"tenant": {
"DomainName": "testorgd2.onmicrosoft.com",
"ExternalId": "d2e1fc1d-30e6-f0b4-cf5b-5f81a9b85f18",
"InternalId": "F8B1229F-350A-D94E-E518-C17E1B2CE465"
}
}
}
]
}'Example response (shortened; OrderData echoes the request as a JSON string and is omitted here):
{
"OrderWithOperations": {
"Order": {
"Id": "0e33fea2-7173-4384-bf17-491139c47011",
"Name": "Microsoft Intune Device ordered",
"OrderNumber": 90572,
"ItemsCount": 1,
"Status": "CommonWorkflow_Start",
"StatusName": "Start",
"OrganizationId": "9819cf3c-fbd9-4a5c-92e7-3ef5922c36ce",
"CreatedByApiUser": "Postman-API",
"IsEditable": true
},
"Operations": [
{
"Id": "9e52a7f1-3c48-4d06-b1a7-000000000002",
"Name": "CreateQuote",
"Rank": 1
},
{
"Id": "9e52a7f1-3c48-4d06-b1a7-000000000004",
"Name": "PlaceOrder",
"RequireValidation": true,
"RequireTermsAgree": true,
"RequireUserMessage": false,
"StartProvision": false,
"TargetStatusId": "00000000-0000-0000-0000-000000000000",
"IsTargetStatusEditable": false,
"Rank": 0
}
]
},
"ValidationResult": {
"OrderedOffersErrors": null,
"IsValid": true
},
"ErrorMessage": null
}From the response, keep two things for step 2:
-
OrderWithOperations.Order.Id: the order ID. - The whole PlaceOrder object from
OrderWithOperations.Operations, in particular itsIdandTargetStatusId.
The full response also lists ScheduleOrder, Delete, OnlyMarkupChange, SaveForLater and Update. To place the order, pick PlaceOrder. Find it by Name rather than by its position in the array or its Rank.
Step 2: Execute the order
Send the order ID, the order content, and the PlaceOrder operation from step 1. A successful call returns HTTP 200 with the order in ProvisionStarted status, and provisioning of the Subscription begins.
Request body fields
| Field | Required | Description |
|---|---|---|
OrderId |
Yes |
OrderWithOperations.Order.Id from step 1. |
OrderCommand |
Yes | The order content: OrderId (same value), OrderName and OrderedOffers, as sent in step 1. Reuse the same OrderItemId values. |
Operation |
Yes | The PlaceOrder object from the step 1 Operations array, copied as returned: Id, Name, RequireValidation, RequireTermsAgree, RequireUserMessage, StartProvision, TargetStatusId, IsTargetStatusEditable, Rank. |
Id or TargetStatusId. Always take them from the step 1 response for the order you are executing.Example request
curl --location 'https://api.appxite.com/orders/operation' \
--header 'X-Referer: <your platform URL>' \
--header 'Ocp-Apim-Subscription-Key: <subscription key>' \
--header 'Authorization: Bearer <token>' \
--header 'Api-Version: 3.1' \
--header 'Environment: Sandbox' \
--header 'X-On-Behalf-Of: 9819CF3C-FBD9-4A5C-92E7-3EF5922C36CE' \
--header 'Content-Type: application/json' \
--data '{
"OrderId": "0e33fea2-7173-4384-bf17-491139c47011",
"OrderCommand": {
"OrderId": "0e33fea2-7173-4384-bf17-491139c47011",
"OrderName": "Microsoft Intune Device ordered",
"OrderedOffers": [
{
"OfferId": "B359AEAC-BF3B-4F19-B4BF-86B4766CF222",
"OrderItemId": "789dc84f-87a9-4b78-8123-0910c11cab5a",
"Quantity": 1,
"CustomerMarkup": 0,
"Name": "Microsoft Intune Device",
"FormData": {
"quantityKey": 1,
"termDuration": "P1Y",
"billingCycle": "Monthly",
"customTermEndDates": "None",
"transitionEligibilities": "none",
"tenant": {
"DomainName": "testorgd2.onmicrosoft.com",
"ExternalId": "d2e1fc1d-30e6-f0b4-cf5b-5f81a9b85f18",
"InternalId": "F8B1229F-350A-D94E-E518-C17E1B2CE465"
}
}
}
]
},
"Operation": {
"Id": "9e52a7f1-3c48-4d06-b1a7-000000000004",
"Name": "PlaceOrder",
"RequireValidation": true,
"RequireTermsAgree": true,
"RequireUserMessage": false,
"StartProvision": false,
"TargetStatusId": "00000000-0000-0000-0000-000000000000",
"IsTargetStatusEditable": false,
"Rank": 0
}
}'Example response (shortened):
{
"OrderWithOperations": {
"Order": {
"Id": "0e33fea2-7173-4384-bf17-491139c47011",
"Name": "Microsoft Intune Device ordered",
"ItemsCount": 1,
"Status": "CommonWorkflow_ProvisionStarted",
"StatusName": "ProvisionStarted",
"IsEditable": false
},
"Operations": [
{
"Id": "9e52a7f1-3c48-4d06-b1a7-000000000013",
"Name": "Delete",
"Rank": 4
}
]
},
"ValidationResult": {
"OrderedOffersErrors": null,
"IsValid": true
},
"ErrorMessage": null
}Check these values to confirm the order was placed:
-
StatusNameisProvisionStarted. -
IsEditableisfalse; the order can no longer be changed. -
ValidationResult.IsValidistrue.
To follow the order afterwards, use the Retrieve an order or List all orders API.
Change the quantity of a subscription
Use this flow to raise or lower the quantity of an existing subscription. The two calls are the same as for a new order; the order content goes in the SubscriptionChanges array instead of OrderedOffers, and each item points at the subscription it changes.
Step 1: Create the change order
A successful call returns HTTP 200 with the order Id, the order in Start status, and the operations you can run next, in the same shape as the new order response.
Request body fields
| Field | Required | Description |
|---|---|---|
OrderName |
Yes | Display name of the order. |
OrderScheduleType |
Yes |
Now applies the change as soon as the order is executed. |
SubscriptionChanges[].ChangeType |
Yes |
SubscriptionChange for a quantity change. |
SubscriptionChanges[].SubscriptionId |
Yes | ID of the existing subscription to change. |
SubscriptionChanges[].OfferId |
Yes | ID of the subscription's Offer. |
SubscriptionChanges[].OrderItemId |
Yes | A UUID you generate for each order item. |
SubscriptionChanges[].Quantity |
Yes | The new quantity of the subscription. Keep it equal to the quantity key in FormData. |
SubscriptionChanges[].Name |
Yes | Display name of the order item. |
SubscriptionChanges[].FormData |
Yes | The offer's form values with the new quantity. Keep the other values as they are on the subscription. |
Example request (Microsoft subscription, quantity changed to 8):
curl --location 'https://api.appxite.com/orders/' \
--header 'X-Referer: <your platform URL>' \
--header 'Ocp-Apim-Subscription-Key: <subscription key>' \
--header 'Authorization: Bearer <token>' \
--header 'Api-Version: 3.1' \
--header 'Environment: Sandbox' \
--header 'X-On-Behalf-Of: 9819CF3C-FBD9-4A5C-92E7-3EF5922C36CE' \
--header 'Content-Type: application/json' \
--data '{
"OrderName": "Quantity change",
"OrderScheduleType": "Now",
"SubscriptionChanges": [
{
"ChangeType": "SubscriptionChange",
"SubscriptionId": "84e35b7d-a6fe-b243-2b09-45fe97583ba4",
"OfferId": "73fb7a23-6eba-463c-9a4d-0158daa213d3",
"OrderItemId": "b9b46ca1-4d51-46e4-a657-768dafedbe88",
"Quantity": 8,
"Name": "Quantity change",
"FormData": {
"quantityKey": 8,
"termDuration": "P1M",
"billingCycle": "Monthly",
"customTermEndDates": "None",
"tenant": {
"DomainName": "testorgd2.onmicrosoft.com",
"ExternalId": "d2e1fc1d-30e6-f0b4-cf5b-5f81a9b85f18",
"InternalId": "F8B1229F-350A-D94E-E518-C17E1B2CE465"
}
}
}
]
}'SubscriptionId must hold the ID of an existing subscription of the Customer in X-On-Behalf-Of. An empty value does not identify the subscription to change.From the response, keep the order Id and the PlaceOrder object for step 2, exactly as for a new order.
Step 2: Execute the change order
Send the order ID, the same SubscriptionChanges content in OrderCommand, and the PlaceOrder operation from step 1. The response has the same shape as the new order execute response; check StatusName and ValidationResult.IsValid the same way.
Request body fields
| Field | Required | Description |
|---|---|---|
OrderId |
Yes |
OrderWithOperations.Order.Id from step 1. |
OrderCommand |
Yes | The order content: OrderId (same value), OrderName, OrderScheduleType and SubscriptionChanges, as sent in step 1. Reuse the same OrderItemId values. |
Operation |
Yes | The PlaceOrder object from the step 1 Operations array, copied as returned. |
Example request
curl --location 'https://api.appxite.com/orders/operation' \
--header 'X-Referer: <your platform URL>' \
--header 'Ocp-Apim-Subscription-Key: <subscription key>' \
--header 'Authorization: Bearer <token>' \
--header 'Api-Version: 3.1' \
--header 'Environment: Sandbox' \
--header 'X-On-Behalf-Of: 9819CF3C-FBD9-4A5C-92E7-3EF5922C36CE' \
--header 'Content-Type: application/json' \
--data '{
"OrderId": "73e8314b-1187-4aed-b251-83caee93da89",
"OrderCommand": {
"OrderId": "73e8314b-1187-4aed-b251-83caee93da89",
"OrderName": "Quantity change",
"OrderScheduleType": "Now",
"SubscriptionChanges": [
{
"ChangeType": "SubscriptionChange",
"SubscriptionId": "84e35b7d-a6fe-b243-2b09-45fe97583ba4",
"OfferId": "73fb7a23-6eba-463c-9a4d-0158daa213d3",
"OrderItemId": "b9b46ca1-4d51-46e4-a657-768dafedbe88",
"Quantity": 8,
"Name": "Quantity change",
"FormData": {
"quantityKey": 8,
"termDuration": "P1M",
"billingCycle": "Monthly",
"customTermEndDates": "None",
"tenant": {
"DomainName": "testorgd2.onmicrosoft.com",
"ExternalId": "d2e1fc1d-30e6-f0b4-cf5b-5f81a9b85f18",
"InternalId": "F8B1229F-350A-D94E-E518-C17E1B2CE465"
}
}
}
]
},
"Operation": {
"Id": "694e0c69-e531-4802-a8e5-64e54e8f096d",
"Name": "PlaceOrder",
"RequireValidation": true,
"RequireTermsAgree": true,
"RequireUserMessage": false,
"StartProvision": false,
"TargetStatusId": "f8a95a47-2588-4f0c-ba9c-1bf74ecc346f",
"IsTargetStatusEditable": false,
"Rank": 0
}
}'Id and TargetStatusId in this example differ from the new order example. They depend on the order's workflow, which is another reason to always take them from the step 1 response.Troubleshooting
422 Unprocessable Entity: FormDataValidationError
The platform validates FormData against the offer's form definition on both calls, for new orders and subscription changes alike. If a key is missing, misspelled, or belongs to a different offer, the call returns 422 Unprocessable Entity, OrderWithOperations is null, and no order is created or executed.
In the example below, a body built for a Microsoft 365 license was sent for the Dynamics 365 Commerce offer. That offer expects different form controls:
{
"OrderWithOperations": null,
"ValidationResult": {
"OrderedOffersErrors": [
{
"OrderItemIndex": 0,
"OrderItemType": "OrderedOffers",
"OrderItemName": "Dynamics 365 Commerce",
"FormDataErrors": {
"Tenant": "The control is required.",
"quantityBox1": "The control is required.",
"quantityKey": "No such control in the form definition.",
"termDuration": "No such control in the form definition.",
"billingCycle": "No such control in the form definition.",
"customTermEndDates": "No such control in the form definition.",
"transitionEligibilities": "No such control in the form definition."
},
"ErrorMessage": "FormDataValidationError"
}
],
"IsValid": false
},
"ErrorMessage": null
}How to read the error:
| Field or message | Meaning | Fix |
|---|---|---|
OrderedOffersErrors / SubscriptionChangesErrors
|
Errors for new order items or for subscription change items. | Look in the array that matches the order type you sent. |
OrderItemIndex |
Zero-based position of the failing item in that array. | Go to that item in your request body. |
The control is required. |
The offer's form requires this key and the request did not send it. | Add the key with a valid value. |
No such control in the form definition. |
The request sent a key this offer's form does not have. | Remove the key. |
IsValid: false |
At least one item failed validation. | Fix every item listed in the error arrays, then resend. |
To prevent this error:
- Build
FormDatafor each offer from its own Retrieve a pricing plan for an offer response. Do not reuse a body from another offer, even one from the same vendor. - Copy key names exactly as the form definition returns them. In the example, this offer expects
TenantandquantityBox1, while the Microsoft Intune offer expectstenantandquantityKey. - Keep
Quantityequal to the value of the offer's quantity key.
RequireValidation: true, the same check runs again on execute. Send the same OrderedOffers or SubscriptionChanges in OrderCommand that passed in step 1.Other issues
| Symptom | Likely cause | Fix |
|---|---|---|
| Request body fails to parse | Malformed JSON, such as a missing comma, quote or bracket | Check the body in a JSON validator and fix the reported position. |
| Order created in Sandbox instead of Production |
Environment header missing |
Send Environment: Production. |
| Request is rejected as unauthorized |
Authorization header missing or the token has expired |
Send Authorization: Bearer <token> with a valid token on both calls. |
| Execute call is rejected for your user | The user's role cannot execute orders | Use a Seller Admin or Account Manager account. |
| Order not found or not executable |
OrderId or operation Id copied from a different order |
Take both from the step 1 response for this order. |
| Subscription change is rejected |
SubscriptionId empty, or it belongs to another Customer |
Send the ID of an existing subscription of the Customer in X-On-Behalf-Of. |
Limitations
- This article covers new orders and quantity changes placed immediately with PlaceOrder. Scheduled changes, cancellations and quotes (CreateQuote) use the same two calls with different bodies and operations; see the request examples in the API reference.
-
PlaceOrder returns
RequireTermsAgree: true. The examples above were placed without a separate terms step. Before placing an order, make sure the vendor's terms are accepted:- Microsoft offers: check whether the Customer has accepted the Microsoft Customer Agreement with Check Microsoft Customer Agreement. If not, record the acceptance with Customer Microsoft agreement (MCuA) acceptance.
- Other vendors: retrieve the terms that apply to the order with the Get Terms and Conditions of offers in order API.
- Depending on your platform's approval settings, an order may stop in Pending Approval instead of ProvisionStarted. Use the Approve an order API to move it forward.
-
FormDatakeys differ per offer and vendor. The keys in this article apply to the offers in the examples only.
Related content
- Create an order (Simplified) — API reference
- Execute an order (Simplified) — API reference
- Approve an order — API reference
- Get Terms and Conditions of offers in order — API reference
- List all customers — API reference
- Retrieve a pricing plan for an offer — API reference
- Check Microsoft Customer Agreement — API reference
- Customer Microsoft agreement (MCuA) acceptance — API reference
Add comment
Please sign in to leave a comment.