How to place orders via API: new subscriptions, changes and cancellations

Appxite

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-Referer header).
  • An Ocp-Apim-Subscription-Key for the Orders product and a valid Bearer token.
  • The Customer organization ID, retrieved with the List all customers API.
  • The OfferId of the product, retrieved with the catalog APIs.
  • The offer's form fields (FormData keys and allowed values), retrieved with the Retrieve a pricing plan for an offer API.
  • For changes to an existing subscription: the SubscriptionId of that subscription, retrieved with the subscription APIs.

In this article

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.

  1. Create the order: send the order content. The response returns the order Id and a list of available Operations (for example CreateQuote, ScheduleOrder, SaveForLater, PlaceOrder).
  2. Execute the order: send the order Id, the same order content in OrderCommand, 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
NOTE! The two calls allow different roles. A Customer user can create an order, but only a Seller Admin or Account Manager can execute it.

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
WARNING! Requests without an 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 its Id and TargetStatusId.

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.
NOTE! Do not hardcode the operation 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:

  • StatusName is ProvisionStarted.
  • IsEditable is false; the order can no longer be changed.
  • ValidationResult.IsValid is true.

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"
                }
            }
        }
    ]
}'
NOTE! 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
    }
}'
NOTE! The operation 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 FormData for 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 Tenant and quantityBox1, while the Microsoft Intune offer expects tenant and quantityKey.
  • Keep Quantity equal to the value of the offer's quantity key.
NOTE! Because PlaceOrder has 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:
  • 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.
  • FormData keys differ per offer and vendor. The keys in this article apply to the offers in the examples only.

Was this article helpful?

0 out of 0 found this helpful

Add comment

Please sign in to leave a comment.