Synchronizing sales from external systems into Vend

A common use of the Vend API is to synchronize sales from external systems, particularly for omni-channel concerns like ecommerce, or an outlet which uses a different POS system.

This tutorial will cover a set of steps for populating and creating a sale.

What you’ll need

Please install Postman, or alternatively, use your favourite software development environment.

With simplicity in mind, we will use a GUI to connect to Vend’s API for this tutorial. Please feel free to use a software development platform instead, but this tutorial will use a GUI tool called Postman.

You should also know how to connect to Vend’s API. See the Quick Start tutorial.

Overview

Key concerns

Typically external systems will need to discover some key retailer information before making a sale, may need to create customer & product records, and may need to execute some fulfillment. When synchronizing entities from an external system into Vend, it is important to avoid duplication of data, and to enter data in a way which will fit for your retailer. Sales have a number of data points which will need to be fulfilled.

  • Some fields (user_id, tax_id, …) will be different for each retailer: you will need to plan ahead for these.
  • You should make a plan for handling tax, payment types, outlets and registers, products, customers, pricing, and users.
  • Avoid duplicates: use unique identifiers and ‘idempotency keys’.
  • Parked sales vs Completed sales.

0. Set up Postman

Set up postman using the Quick Start tutorial. This will guide you through two different approaches to Auth. Take note of the Postman Tips about environments and variables.

  • In Postman, create a ‘New Collection’. Call it ‘Sync a sale into Vend’ or similar.
  • Set up authorization on this collection, using either a Bearer Token (personal token) or OAuth flow, as explained in Quick Start.

1. Discover Dependencies

A sale has a number of dependencies, which are typically represented as UUIDs in the Sale request payload. These UUID values differ for each retailer.

1a. Fetch retailer information.

The retailer entity has a number of useful fields relating to tax, special product records, gift cards & store credit.

  • Create a new Request within your collection. GET, https://{{domain_prefix}}.vendhq.com/api/2.0/retailer. Click Send.
    • From the result, you may want the data.tax_exclusive (this is described using javascript-style notation).

1b. Register ID

You will need a way to identify the register for your sale. The retailer may have/want a specific register for your sales channel. You can fetch the full list of registers. Each is linked to an outlet. A good time to agree on a register might be while pairing an addon with a retailer account.

  • Create a new Request within your collection. GET, https://{{domain_prefix}}.vendhq.com/api/2.0/registers. Click Send.
    • Note the data.[x].id (having identified the correct register.
    • If you need to use the outlet to decide, use /api/2.0/outlets (or /api/2.0/outlets/{{outlet_id}} for a single outlet)

1c. User

Do you want sales to be associated with specific cashiers, with the retailer’s primary user, or even the addon’s user account?

  • Let’s assume the primary user.
    • Create a new Request within your collection. GET, https://{{domain_prefix}}.vendhq.com/api/2.0/users. Click Send.
    • Note the id of the user whose data.[x].is_primary_user === true
    • For specific users, check the username.

1d. Taxes

Tax values might be dictated by the external system, although it’s important to recognise a few Vend configuration items. Vend retailer accounts can be either ‘tax inclusive’ or ‘tax exclusive’.

  • Taxes: tax codes, tax exclusive/inclusive?
  • Pricing: promotions, pricebooks. calculate prices based on promotions, pricebooks and list prices?
  • Customers
  • Products

The tax amount and tax_id should always be sent to /api/register_sales.

You can retrieve the relevant tax_id from /api/2.0/taxes.

Note: ALL retailers have a special tax with the name “No Tax”, which represents “No Tax”.

To post a tax-free sale, please fill tax_id field using the id of the tax named "name": "No Tax", along with a value of zero: "tax": 0.

1e. Payment types

Payment types: does the external system have matching payment types to Vend? In some cases, a retailer may have several payment types. A good time to agree on a payment type might be while pairing an addon with a retailer account.

  • Create a new Request within your collection. GET, https://{{domain_prefix}}.vendhq.com/api/2.0/payment_types. Click Send.
    • NOTE: there may be several providers for a given category of payment type. If you don’t know which type to use, it is best to agree directly with a retailer during setup.
    • Payment types have an id, which is unique for that retailer, and should be used for the sale request.
    • The payment_type part of the record can be used to help identify/narrow your search for the correct payment type. The data.[x].payment_type can be used to identify different types, such as "category": "cash" or "name": "Gift cards".

2. Search/create a customer

You may need to verify that your customer record exists in Vend. The search API allows you search by customer_code or by email, which is useful if your external system doesn’t store the Vend customer codes.

If it doesn’t exist, then create one:

  • Create a new Request within your collection. GET, https://{{domain_prefix}}.vendhq.com/api/2.0/search?type=customers&customer_code=abc or https://{{domain_prefix}}.vendhq.com/api/2.0/search?type=customers&email=abc@example.com. Click Send.
  • If found, note the ID of the customer.
  • If not found, create a customer.
  • To create a customer, create a new Request within your collection. Method POST, https://{{domain_prefix}}.vendhq.com/api/2.0/customers. This will create the customer and then return the id.

3. Search/create a product

You may need to verify that your product record exists in Vend - search by sku or handle to uniquely identify the product. If it doesn’t exist, then create one:

  • Create a new Request within your collection. GET, https://{{domain_prefix}}.vendhq.com/api/2.0/search?type=product&sku=abc. Click Send.
  • If found, note the ID of the product in the response.
  • If not found, create a product.
  • To create a product, create a new Request within your collection. Method POST, https://{{domain_prefix}}.vendhq.com/api/products. This will create the product and then return the id.

Warning: If this product you are creating is a variant of another product you will first need to read the documentation on variant products before doing this to ensure the new product is actually a variant of the parent product. The key thing to note about variants is that they have the same handle.

4. Line Items

Line items are posted in the register_sale_products array.

Each item has

Field Source Notes
register_id /api/2.0/registers Typically the same register as the sale itself.
product_id /api/2.0/search Search or create a product.
tax_id /api/2.0/taxes See ‘default’. Also see /api/2.0/retailer for no_tax_group_id and tax_exclusive.
price /api/2.0/products/{id} Typically, use product record for pricing.
status For CLOSED sales, prefer CONFIRMED.
price_set Use 1 to avoid recalculating totals.
cost Unit cost of the item.
discount Discount value of the line item.
tax /api/2.0/taxes The unit tax value associated with this line item.
loyalty_value The value of loyalty that will be incurred by the customer for this line item.
sequence Order number of the line item.
quantity Quantity of products for the line item.

5. Create the sale

Finally, we are ready to create the sale in Postman. To create a sale we need to POST to a v0.9 endpoint https://{{domain_prefix}}/api/register_sales. The description of the payload to send to this endpoint can be found here. However, not all the fields are required.

An example of a simple sales payload looks as follows:

{
    "source_id": "Your-Source-ID",
    "register_id": "02dcd191-ae2b-11e9-f336-cd2e6d8a318a",
    "user_id": "0a6f6e36-8bba-11ea-f3d6-b41420a1a15f",
    "status": "CLOSED",
    "register_sale_products": [
        {
            "product_id": "02dcd191-aeba-11e9-f336-cd2e6da4ef2e",
            "quantity": 1.0,
            "price": 10.00,
            "tax": 1.15,
            "tax_id": "02dcd191-ae2b-11e9-f336-cd2e6d864598"
        }
    ]
}

This contains enough information for the Vend system to be able to correctly register a sale. The source_id is a field you can use to push the unique id for this sale from your system to Vend so that you are able to easily match sales in the two systems. It is not required.

Definitions

The sale object

Attribute Sample Value Req/Opt Description
register_id "02dcd191-ae2b-11e9-f336-cd2e6d8a318a" required Valid id of register to assign the sale to.
user_id "0a6f6e36-8bba-11ea-f3d6-b41420a1a15f" required Valid id of a Vend user associated with the sale.
status "CONFIRMED" required For sale statuses see here.
register_sale_products [] required An array of line items - definition of attributes in a table below. When updating a sale all existing products need to be included in the payload or they will be deleted.

The register sale product object

Attribute Sample Value Req/Opt Description
product_id "02dcd191-aeba-11e9-f336-cd2e6da4ef2e" required Vend product id.
quantity 1 required Product quantity.
price 10.00 required Unit price, tax exclusive.
tax 1.15 required Tax value.
tax_id "02dcd191-ae2b-11e9-f336-cd2e6d864598" required Tax id as retrieved from /api/2.0/taxes.

The response to this POST looks as follows:

{
    "register_sale": {
        "id": "0a6f6e36-8bba-11ea-f3d6-e713d01f1a4b",
        "source": "USER",
        "source_id": "Your-Source-ID",
        "register_id": "02dcd191-ae2b-11e9-f336-cd2e6d8a318a",
        "market_id": "1",
        "customer_id": "02dcd191-ae2b-11e9-f336-cd2e6d848f93",
        "customer_name": " ",
        "customer": {
            "id": "02dcd191-ae2b-11e9-f336-cd2e6d848f93",
            "name": " ",
            "customer_code": "WALKIN",
            "customer_group_id": "02dcd191-ae2b-11e9-f336-cd2e6d846a75",
            "customer_group_ids": [],
            "customer_group_name": "All Customers",
            "first_name": "",
            "last_name": "",
            "company_name": "",
            "phone": "",
            "mobile": "",
            "fax": "",
            "email": "",
            "do_not_email": "",
            "twitter": "",
            "website": "",
            "physical_address1": "",
            "physical_address2": "",
            "physical_suburb": "",
            "physical_city": "",
            "physical_postcode": "",
            "physical_state": "",
            "physical_country_id": "",
            "postal_address1": "",
            "postal_address2": "",
            "postal_suburb": "",
            "postal_city": "",
            "postal_postcode": "",
            "postal_state": "",
            "postal_country_id": "",
            "updated_at": "2019-09-02 03:04:50",
            "deleted_at": "",
            "balance": "0",
            "year_to_date": "0",
            "date_of_birth": "",
            "sex": "",
            "custom_field_1": "",
            "custom_field_2": "",
            "custom_field_3": "",
            "custom_field_4": "",
            "note": "",
            "contact": {
                "company_name": "",
                "phone": "",
                "email": ""
            }
        },
        "user_id": "0a6f6e36-8bba-11ea-f3d6-b41420a1a15f",
        "user_name": "Cashier1",
        "sale_date": "2020-08-25 20:44:47",
        "created_at": "2020-08-25 20:44:47",
        "updated_at": "2020-08-25 20:44:47",
        "total_price": 10,
        "total_cost": 2,
        "total_tax": 1.15,
        "tax_name": "GST",
        "note": "",
        "status": "CLOSED",
        "short_code": "zvgydg",
        "invoice_number": "39",
        "accounts_transaction_id": "",
        "return_for": "",
        "register_sale_products": [
            {
                "id": "0a6f6e36-8bba-11ea-f3d6-e713d0223fba",
                "product_id": "02dcd191-aeba-11e9-f336-cd2e6da4ef2e",
                "register_id": "02dcd191-ae2b-11e9-f336-cd2e6d8a318a",
                "sequence": "0",
                "handle": "FreshlySqueezedJuice",
                "sku": "10012",
                "name": "Freshly Squeezed Juice",
                "quantity": 1.0,
                "price": 10,
                "cost": 2,
                "price_set": 0,
                "discount": 0,
                "loyalty_value": 0,
                "tax": 1.15,
                "tax_id": "02dcd191-ae2b-11e9-f336-cd2e6d864598",
                "tax_name": "GST",
                "tax_rate": 0.15,
                "tax_total": 1.15,
                "price_total": 10,
                "display_retail_price_tax_inclusive": "1",
                "status": "CONFIRMED",
                "attributes": [
                    {
                        "name": "line_note",
                        "value": ""
                    }
                ],
                "tax_components": [
                    {
                        "rate_id": "6d897140-cd2e-11e9-9336-02dcd191ae2b",
                        "total_tax": 1.15
                    }
                ]
            }
        ],
        "totals": {
            "total_tax": 1.15,
            "total_price": 10,
            "total_payment": 0,
            "total_to_pay": 11.15
        },
        "register_sale_payments": [],
        "taxes": [
            {
                "id": "6d897140-cd2e-11e9-9336-02dcd191ae2b",
                "tax": 1.15,
                "name": "GST",
                "rate": 0.15
            }
        ]
    }
}

As you can see a significant amount of information is returned if the sale is successful which can be used to update or validate entities like the customer and tax rate for example.

Next we will look at a sale with a complete payload.

{
    "source_id": "Your-Source-ID",
    "register_id": "02dcd191-ae2b-11e9-f336-cd2e6d8a318a",
    "customer_id": "02dcd191-ae2b-11e9-f336-cd2e6d848f93",
    "user_id": "02dcd191-aeba-11e9-f336-cd2e6da4ef2e",
    "sale_date": "2016-05-05 23:35:34",
    "note": "On special",
    "status": "CLOSED",
    "short_code": "mlzs94",
    "invoice_number": "MR-1484-NZ",
    "invoice_sequence": 1484,
    "accounts_transaction_id": "",
    "register_sale_products": [
        {
            "product_id": "02dcd191-aeba-11e9-f336-cd2e6da4ef2e",
            "register_id": "02dcd191-ae2b-11e9-f336-cd2e6d8a318a",
            "sequence": "0",
            "quantity": 1.0,
            "price": 22,
            "cost": 20,
            "price_set": 0,
            "discount": 0,
            "loyalty_value": 0,
            "tax": 3.3,
            "tax_id": "6d897140-cd2e-11e9-9336-02dcd191ae2b",
            "status": "CONFIRMED",
            "attributes": [
                {
                    "name": "line_note",
                    "value": "large"
                }
            ]
        }
    ],
    "register_sale_payments": [
        {
            "register_id": "02dcd191-ae2b-11e9-f336-cd2e6d8a318a",
            "retailer_payment_type_id": "5569147f-f4d3-4fe1-909d-175e2c59af06",
            "payment_date": "2016-05-05 23:35:34",
            "amount": 25.3
        }
    ]
}

Definitions

The sale object

Attribute Sample Value Req/Opt Description
source_id "Your-Source-ID" optional The id in your source system
register_id "b1e198a9-f019-11e3-a0f5-b8ca3a64f8f4" required Valid id of register to assign the sale to.
customer_id "06e35f89-3783-11e6-ec7e-13193f7bd2ed" optional Valid id of the customer associated with the sale.
user_id "b1ed6158-f019-11e3-a0f5-b8ca3a64f8f4" required Valid id of a Vend user associated with the sale.
sale_date "2016-05-05 23:35:34" optional By default current time will be assigned
note "" optional A note to be attached to the sale
status "CLOSED" required For sale statuses see here.
short_code "mlzs94" optional Used for loyalty claiming, can be overwritten but must be unique.
invoice_number "MR-1484-NZ" optional Invoice number, if omitted one will be assigned by Vend.
invoice_sequence 1484 optional Numeric part of the invoice number.
"accounts_transaction_id" "" optional Xero reference invoice ID. Only editable for ONACCOUNT sales.
register_sale_products [] required An array of line items - definition of attributes in a table below. When updating a sale all existing products need to be included in the payload or they will be deleted.
register_sale_payments [] optional An array of payments - definition of attributes in a table below.

The register sale product object

Attribute Sample Value Req/Opt Description
product_id "b1d87b58-f019-11e3-a0f5-b8ca3a64f8f4" required Vend product id.
register_id "b1e198a9-f019-11e3-a0f5-b8ca3a64f8f4" optional A line item can be added on a different register than the sale was initially created.
sequence 0 optional Order of the line item in the sale, safe to ignore.
quantity 1 required Product quantity.
price 22 required Unit price, tax exclusive.
cost 20 optional Cost to be used for margin calculations.
price_set 0 optional Boolean value describing if sale was modified manually. Setting this to 1 will prevent price recalculation in the sell screen.
discount 0 optional If the price was set manually, discount can be declared here for reporting.
loyalty_value 2.0 optional The value by which customer’s loyalty balance should be increased.
tax 3.3 required Tax value.
tax_id "b1d192bc-f019-11e3-a0f5-b8ca3a64f8f4" required Tax id as retrieved from /api/2.0/taxes.
status "CONFIRMED" optional CONFIRMED is the only meaningful, non-default option. Makes it impossible to remove product from the sale.

The register sale payment object

Attribute Sample Value Req/Opt Description
register_id "b1e198a9-f019-11e3-a0f5-b8ca3a64f8f4" optional A payment can also be accepted in a sale different than the sale originate from.
retailer_payment_type_id "b1e1d70e-f019-11e3-a0f5-b8ca3a64f8f4" required Payment type id - that’s the id of payment types retrieved from /api/2.0/payment_types.
payment_date "2016-05-05 23:35:34" optional By default current time will be assigned.
amount 25.3 required Payment amount.

The response from this payload looks as follows:

{
    "register_sale": {
        "id": "0a6f6e36-8bba-11ea-f3d6-e72122b5c381",
        "source": "USER",
        "source_id": "Your-Source-ID",
        "register_id": "02dcd191-ae2b-11e9-f336-cd2e6d8a318a",
        "market_id": "1",
        "customer_id": "02dcd191-ae2b-11e9-f336-cd2e6d848f93",
        "customer_name": " ",
        "customer": {
            "id": "02dcd191-ae2b-11e9-f336-cd2e6d848f93",
            "name": " ",
            "customer_code": "WALKIN",
            "customer_group_id": "02dcd191-ae2b-11e9-f336-cd2e6d846a75",
            "customer_group_ids": [],
            "customer_group_name": "All Customers",
            "first_name": "",
            "last_name": "",
            "company_name": "",
            "phone": "",
            "mobile": "",
            "fax": "",
            "email": "",
            "do_not_email": "",
            "twitter": "",
            "website": "",
            "physical_address1": "",
            "physical_address2": "",
            "physical_suburb": "",
            "physical_city": "",
            "physical_postcode": "",
            "physical_state": "",
            "physical_country_id": "",
            "postal_address1": "",
            "postal_address2": "",
            "postal_suburb": "",
            "postal_city": "",
            "postal_postcode": "",
            "postal_state": "",
            "postal_country_id": "",
            "updated_at": "2019-09-02 03:04:50",
            "deleted_at": "",
            "balance": "0",
            "year_to_date": "0",
            "date_of_birth": "",
            "sex": "",
            "custom_field_1": "",
            "custom_field_2": "",
            "custom_field_3": "",
            "custom_field_4": "",
            "note": "",
            "contact": {
                "company_name": "",
                "phone": "",
                "email": ""
            }
        },
        "user_id": "02dcd191-ae2b-11e9-f336-cd2e6d8a5806",
        "user_name": "scott.newton@vendhq.com",
        "sale_date": "2016-05-05 23:35:34",
        "created_at": "2020-08-25 22:20:09",
        "updated_at": "2020-08-25 22:20:09",
        "total_price": 22,
        "total_cost": 2,
        "total_tax": 3.3,
        "tax_name": "GST",
        "note": "On special",
        "status": "CLOSED",
        "short_code": "mlzs94",
        "invoice_number": "MR-1484-NZ",
        "accounts_transaction_id": "",
        "return_for": "",
        "register_sale_products": [
            {
                "id": "0a6f6e36-8bba-11ea-f3d6-e72122ba2156",
                "product_id": "02dcd191-aeba-11e9-f336-cd2e6da4ef2e",
                "register_id": "02dcd191-ae2b-11e9-f336-cd2e6d8a318a",
                "sequence": "0",
                "handle": "FreshlySqueezedJuice",
                "sku": "10012",
                "name": "Freshly Squeezed Juice",
                "quantity": 1.0,
                "price": 22,
                "cost": 2,
                "price_set": 0,
                "discount": 0,
                "loyalty_value": 0,
                "tax": 3.3,
                "tax_id": "02dcd191-ae2b-11e9-f336-cd2e6d864598",
                "tax_name": "GST",
                "tax_rate": 0.15,
                "tax_total": 3.3,
                "price_total": 22,
                "display_retail_price_tax_inclusive": "1",
                "status": "CONFIRMED",
                "attributes": [
                    {
                        "name": "line_note",
                        "value": "large"
                    }
                ],
                "tax_components": [
                    {
                        "rate_id": "6d897140-cd2e-11e9-9336-02dcd191ae2b",
                        "total_tax": 3.3
                    }
                ]
            }
        ],
        "totals": {
            "total_tax": 3.3,
            "total_price": 22,
            "total_payment": 25.3,
            "total_to_pay": 0
        },
        "register_sale_payments": [
            {
                "id": "0a6f6e36-8bba-11ea-f3d6-e72122c0e15c",
                "payment_type_id": "1",
                "register_id": "02dcd191-ae2b-11e9-f336-cd2e6d8a318a",
                "retailer_payment_type_id": "5569147f-f4d3-4fe1-909d-175e2c59af06",
                "name": "Cash",
                "label": "Cash",
                "payment_date": "2016-05-05T23:35:34Z",
                "amount": 25.3
            }
        ],
        "taxes": [
            {
                "id": "6d897140-cd2e-11e9-9336-02dcd191ae2b",
                "tax": 3.3,
                "name": "GST",
                "rate": 0.15
            }
        ]
    }
}

Gotchas

  • An incorrect payload will not necessarily cause an error when posted to the /api/register_sales endpoint. Instead, an empty sale, with no products and payments will be created and the server will respond with 200 OK.
  • The sale should never be posted with OPEN status. That status is reserved for the client-side applications and should never make it to the server.
  • Promotions cannot currently be used when registering a sale.

Parked vs completed sales

To park a sale in the Vend system set the status to SAVED. For completed sales you would normally use CLOSED. However, see below Handle Fulfillment for sales where the sale is complete but still needs to be delivered or collected.

6. Handle Fulfillment

For a tutorial on using our fulfillment statuses for omnichannel workflows, please review our tutorial.

Vend supports click and collect as well as sales that need to be dispatched to the customer. For click and collect the statuses are AWAITING_PICKUP for when the sale is complete but not yet picked up and PICKED_UP_CLOSED for when the customer does the actual pickup. For sales that need to be sent to the customer we have AWAITING_DISPATCH which can be changed to DISPATCHED_CLOSED once the sale has been sent. This leads us nicely on to our next topic of editing a sale.

7. Editing a Sale

To edit a sale, a POST request with the appropriate payload should be posted to the API 0.9 /api/register_sales endpoint.

WARNING: The current sales endpoint (/api/register_sales) behaves a bit differently than other POST endpoints. Where in the majority of other endpoints it is possible to submit partial payloads, the sale has to be submitted with all the attributes of an existing sale.\nE.g. if a sale is submitted without the line items (register_sale_products) which existed on it previously, they will be deleted and replaced with whatever is posted.

WARNING: It’s worth noticing that there are a few important differences in payload attribute names between API 0.9 and API 2.0. So, if you get the sale data from API 2.0 and using it compose a payload to post back to API 0.9 you will have to, for example, rename line_items to register_sale_products.

Changing the status

This is done, by changing the value of the status attribute of the sale. As mentioned above in the warning, you need to provide a full sale payload for this request.

The 2 most common use-cases for changing the status is completing or voiding a sale. They are worth mentioning separately as they usually require a differently prepared payload.

More about sale statuses here.

Completing a sale

When this is done, there are usually 2 things that should happen in the payload:

  1. A payment should be added to pay off the outstanding balance of the sale. This can be done as described above.
  2. The status of the sale should be changed to one of: CLOSED, LAYBY_CLOSED, ONACCOUNT_CLOSED, PICKUP_CLOSED or DISPATCHED_CLOSED.

So our minimal payload would become:

{
    "source_id": "Your-Source-ID",
    "register_id": "02dcd191-ae2b-11e9-f336-cd2e6d8a318a",
    "user_id": "0a6f6e36-8bba-11ea-f3d6-b41420a1a15f",
    "status": "CLOSED",
    "register_sale_products": [
        {
            "product_id": "02dcd191-aeba-11e9-f336-cd2e6da4ef2e",
            "quantity": 1.0,
            "price": 10.00,
            "tax": 1.15,
            "tax_id": "02dcd191-ae2b-11e9-f336-cd2e6d864598"
        }
    ],
    "register_sale_payments": [
        {
            "register_id": "02dcd191-ae2b-11e9-f336-cd2e6d8a318a",
            "retailer_payment_type_id": "5569147f-f4d3-4fe1-909d-175e2c59af06",
            "payment_date": "2016-05-05 23:35:34",
            "amount": 11.15
        }
    ]
}

Voiding a sale

This simply requires changing the status of the sale to VOIDED.

WARNING: Once the status of a sale is set to VOIDED it shouldn’t be changed to anything else. Doing so may result in corrupting inventory and payment data.

Adding line items

To add a new line item to the sale a new item should be added to the register_sale_products array in the sale payload. Below is an example of the minimal set of attributes that should be included:

{
	"product_id": "02dcd191-aeba-11e9-f336-cd2e6da4ef2e",
	"quantity": 1.0,
	"price": 22,
	"tax": 3.3,
	"tax_id": "02dcd191-ae2b-11e9-f336-cd2e6d864598"
}

So, in the case of a parked (SAVED) sale with no previous payments on it, the payload should look like this:

{
	"id": "a604d16b-a999-a82b-11e7-2ddc0a37c22b",
	"source": "USER",
	"source_id": "",
	"register_id": "02dcd191-ae2b-11e9-f336-cd2e6d8a318a",
	"market_id": "1",
	"customer_id": "02dcd191-ae2b-11e9-f336-cd2e6d848f93",
	"user_id": "02dcd191-ae2b-11e9-f336-cd2e6d8a5806",
	"user_name": "api@vendhq.com",
	"sale_date": "2017-04-30 22:29:00",
	"created_at": "2017-04-30 22:29:00",
	"updated_at": "2017-04-30 22:29:00",
	"total_price": 12,
	"total_cost": 8.73,
	"total_tax": 1.8,
	"tax_name": "GST",
	"note": "",
	"status": "SAVED",
	"short_code": "aidgvq",
	"invoice_number": "MR-1704-NZ",
	"accounts_transaction_id": "",
	"return_for": "",
	"register_sale_products": [{
			"id": "a604d16b-a999-963a-11e7-2df456273584",
			"product_id": "02dcd191-aeba-11e9-f336-cd2e6da4ef2e",
			"register_id": "02dcd191-ae2b-11e9-f336-cd2e6d8a318a",
			"sequence": "0",
			"handle": "tshirt",
			"sku": "tshirt-white",
			"name": "T-shirt",
			"quantity": 1.0,
			"price": 12,
			"cost": 8.73,
			"price_set": 0,
			"discount": 0,
			"loyalty_value": 1.38,
			"tax": 1.8,
			"tax_id": "02dcd191-ae2b-11e9-f336-cd2e6d864598",
			"tax_name": "GST",
			"tax_rate": 0.15,
			"tax_total": 1.8,
			"price_total": 12,
			"display_retail_price_tax_inclusive": "1",
			"status": "SAVED",
			"attributes": [{
				"name": "line_note",
				"value": ""
			}]
		},
		{
			"product_id": "02dcd191-aeba-11e9-f336-cd2e6da4ef2e",
			"quantity": 1,
			"price": 12,
			"tax": 1.8,
			"tax_id": "02dcd191-ae2b-11e9-f336-cd2e6d864598"
		}
	]
}

Adding payments

To add a payment to a sale the following object should be added to the register_sale_payments array in the payload:

{
	"retailer_payment_type_id": "5569147f-f4d3-4fe1-909d-175e2c59af06",
	"payment_date": "2016-09-19T20:28:03Z",
	"amount": -230.12
}

NOTE: _The payment_date attribute is optional. If not supplied the current date/time will be used.

  • The retailer_payment_type_id attribute is the id of a valid payment type that can be retrieved from /api/payment_types or /api/2.0/payment_types endpoints._

In the case of a sale where some payments have been added previously, the sale payload to be posted should look like that:

{
	"id": "a604d16b-a999-9212-11e7-2ae82f545149",
	"source": "USER",
	"source_id": "",
	"register_id": "02dcd191-ae2b-11e9-f336-cd2e6d8a318a",
	"market_id": "1",
	"customer_id": "02dcd191-ae2b-11e9-f336-cd2e6d848f93",
	"user_id": "02dcd191-ae2b-11e9-f336-cd2e6d8a5806",
	"user_name": "api@vendhq.com",
	"sale_date": "2017-04-27 04:14:33",
	"created_at": "2017-04-27 04:14:39",
	"updated_at": "2017-04-27 04:14:39",
	"total_price": 1200,
	"total_cost": 205.52,
	"total_tax": 180,
	"tax_name": "GST",
	"note": "",
	"status": "ONACCOUNT",
	"short_code": "nydtpj",
	"invoice_number": "MR-1701-NZ",
	"accounts_transaction_id": "",
	"return_for": "",
	"register_sale_products": [{
		"id": "a604d16b-a999-9212-11e7-2affc316bc8c",
		"product_id": "02dcd191-aeba-11e9-f336-cd2e6da4ef2e",
		"register_id": "02dcd191-ae2b-11e9-f336-cd2e6d8a318a",
		"sequence": "0",
		"handle": "MoustachePotion1",
		"sku": "10012",
		"name": "Moustache Potion",
		"quantity": 1.0,
		"price": 1200,
		"cost": 205.52,
		"price_set": 0,
		"discount": -115,
		"loyalty_value": 138,
		"tax": 180,
		"tax_id": "02dcd191-ae2b-11e9-f336-cd2e6d864598",
		"tax_name": "GST",
		"tax_rate": 0.15,
		"tax_total": 180,
		"price_total": 1200,
		"display_retail_price_tax_inclusive": "1",
		"status": "CONFIRMED",
		"attributes": [{
			"name": "line_note",
			"value": ""
		}]
	}],
	"register_sale_payments": [{
		"id": "a604d16b-a999-a251-11e7-2afff8a8a128",
		"payment_type_id": "1",
		"register_id": "02dcd191-ae2b-11e9-f336-cd2e6d8a318a",
		"retailer_payment_type_id": "5569147f-f4d3-4fe1-909d-175e2c59af06",
		"name": "Cash",
		"label": "Cash",
		"payment_date": "2017-04-27 04:14:13",
		"amount": 200
	},
	{
		"retailer_payment_type_id": "5569147f-f4d3-4fe1-909d-175e2c59af06",
		"payment_date": "2017-04-27 04:14:13",
		"amount": 400
	}],
}

Creating a return

Creating returns is described in a separate article here: Sale returns.

Notes

Avoiding Duplicates

The best way of avoiding duplicates is to store the Vend sale id that is returned when you create a sale in the Vend system in the external system so that you can always send the id for the sale when updating a sale. This avoids unnecessary lookups and reduces the risk of duplicates significantly.

if that is not possible then the search API allows you to search for sales by date_from, date_to, status, invoice_number, customer_id, user_id, or outlet_id.

What Next?

Now that you know how to access Vend’s API, please explore the documentation - we recommend visiting the API Introduction document, and the sales tutorials.