This guide is for creating a checkout experience where items will be added to a Locally cart individually

Creating a new cart and adding items for BOPIS/ROPIS

To begin, create a new cart and add an item to it for either BOPIS (Buy Online, Pick Up In Store) or ROPIS (Reserve Online, Pick Up In Store).

BOPIS Request:

curl 'https://www.locally.com/headless/api/1.0/cart/items/add?store_id={{STORE_ID}}&upc={{UPC}}&host_domain={{HOST_DOMAIN}}' \
--header 'Locally-Api-Token: {{API_TOKEN}}'

Please provide the store_id, UPC, and host_domain along with the required API token. For host_domain, if the call originates from https://www.example.com, set the value as www.example.com.

To flag an order for ROPIS, add the hold parameter:

ROPIS Request:

curl 'https://www.locally.com/headless/api/1.0/cart/items/add?hold=true&store_id={{STORE_ID}}&upc={{UPC}}&host_domain={{HOST_DOMAIN}}' \
--header 'Locally-Api-Token: {{API_TOKEN}}'

For guidance on finding a store_id for a given UPC, refer to this link. To view acquisition options for a store and UPC combination, study the data.markers[].acquisition_options object in the aforementioned link's response.

Successful Response (HTTP Response == 200, success: true):

{
    "success": true,
    "data": {
        "n_in_cart": 1,
        "cart_hash": "OV9",
        "cart_content": {
            "user": {
                "id": 999,
                "email": "[email protected]",
                "first_name": "",
                "last_name": "",
                "phone": "",
                "preferred_zip": "60605",
                "preferred_country": "US"
            },
            "cart": {
                "id": 999,
                "user_id": 999,
                "hash": "OV9",
                "subtotal": 100,
                "tax_total": 10.10,
                "shipping_cost_total": 0,
                "grand_total": 110.10,
                "first_name": "",
                "last_name": "",
                "address": "",
                "address_2": "",
                "city": "",
                "state": "",
                "zip": "",
                "email": "",
                "phone": "",
                "country": "",
                "locale": "en-us",
                "origin": "www.example.com",
                "for_hold": 0,
                "for_delivery": 0,
                "delivery_first_name": "",
                "delivery_last_name": "",
                "delivery_address": "",
                "delivery_address_2": "",
                "delivery_city": "",
                "delivery_state": "",
                "delivery_zip": "",
                "delivery_country": "",
                "delivery_fee_total": 0,
                "is_test": 0,
                "delivery_choice": "",
                "store_delivery_fee": 0,
                "delivery_notes": "",
                "coupon_discount_total": 0,
                "coupon_code": "",
                "coupon_discount_reason": "",
                "for_ship_to_store": 0,
                "cart_currency": "USD",
                "items": [
                    {
                        "id": 999,
                        "qty": 1,
                        "upc": "000000000000",
                        "product_name": "ACME Outfitters Widget",
                        "product_company_name": "ACME Oufitters",
                        "product_image": "image.jpg",
                        "product_color": "Red",
                        "product_size": "M",
                        "product_price": 100,
                        "product_id": 999999,
                        "store_id": 99999,
                        "subtotal": 100,
                        "tax_total": 10.10,
                        "shipping_cost": 0,
                        "grand_total": 110.10,
                        "qty_available": 10,
                        "message": "",
                        "fulfillment_type": 0,
                        "product_currency": "USD",
                        "product_company_id": 31,
                        "coupon_discount_total": 0,
                        "vat": 0,
                        "tax_rate": 10.25,
                        "product": {
                            "id": 999999,
                            "name": "ACME Outfitters Widget",
                            "company_id": 31,
                            "style_number": "99999",
                            "image": "image.jpg",
                            "long_description": "Lorem ipsum.",
                            "average_rating": 5,
                            "n_reviews": 1,
                            "upc_data": null,
                            "company": null
                        }
                    }
                ],
                "custom_cart_text": ""
            },
            "country": {
                "id": 99,
                "name": "United States",
                "code": "US",
                "primary_currency": "USD",
                "secondary_currency": "",
                "primary_locale": "en-us",
                "phone_prefix": "1",
                "display_marketing_consent": "",
                "consent_checkbox_default_state": 0,
                "code_3": "USA",
                "display_brand_marketing_consent": ""
            },
            "cart_error": 0,
            "cart_disclaimer": "By continuing you agree to our <a href=https:\/\/www.locally.com\/terms target=_blank>terms of service<\/a>",
            "cart_store": {
                "id": 99999,
                "name": "ACME Oufitters Chicago",
                "address": "123 ACME St.",
                "zip": "00000",
                "company_id": 99,
                "lat": "41.91136867",
                "lng": "-87.67771437",
                "phone": "(555) 555-5555",
                "mon_time_open": 1100,
                "mon_time_close": 1900,
                "tue_time_open": 1100,
                "tue_time_close": 1900,
                "wed_time_open": 1100,
                "wed_time_close": 1900,
                "thu_time_open": 1100,
                "thu_time_close": 1900,
                "fri_time_open": 1100,
                "fri_time_close": 1900,
                "sat_time_open": 1100,
                "sat_time_close": 1900,
                "sun_time_open": 1100,
                "sun_time_close": 1700,
                "address_2": "",
                "city": "Chicago",
                "state": "IL",
                "country": "US",
                "timezone": "America\/Chicago",
                "average_rating": 5,
                "n_reviews": 3,
                "image": "image.jpg",
                "web_address": "https:\/\/www.example.com",
                "is_temporarily_closed": 0,
                "area": null,
                "store_hours": [
                    {
                        "id": 1,
                        "store_id": 99999,
                        "type": 0,
                        "custom_label": null,
                        "dow": 0,
                        "single_day": null,
                        "closed": 0,
                        "start_time": 700,
                        "end_time": 2000,
                        "group": 1,
                        "is_closed": 0
                    },
                    {
                        "id": 2,
                        "store_id": 99999,
                        "type": 0,
                        "custom_label": null,
                        "dow": 1,
                        "single_day": null,
                        "closed": 0,
                        "start_time": 700,
                        "end_time": 2000,
                        "group": 1,
                        "is_closed": 0
                    },
                    {
                        "id": 3,
                        "store_id": 99999,
                        "type": 0,
                        "custom_label": null,
                        "dow": 2,
                        "single_day": null,
                        "closed": 0,
                        "start_time": 700,
                        "end_time": 2000,
                        "group": 1,
                        "is_closed": 0
                    },
                    {
                        "id": 4,
                        "store_id": 99999,
                        "type": 0,
                        "custom_label": null,
                        "dow": 3,
                        "single_day": null,
                        "closed": 0,
                        "start_time": 700,
                        "end_time": 2000,
                        "group": 1,
                        "is_closed": 0
                    },
                    {
                        "id": 5,
                        "store_id": 99999,
                        "type": 0,
                        "custom_label": null,
                        "dow": 4,
                        "single_day": null,
                        "closed": 0,
                        "start_time": 700,
                        "end_time": 2000,
                        "group": 1,
                        "is_closed": 0
                    },
                    {
                        "id": 6,
                        "store_id": 99999,
                        "type": 0,
                        "custom_label": null,
                        "dow": 5,
                        "single_day": null,
                        "closed": 0,
                        "start_time": 700,
                        "end_time": 2000,
                        "group": 1,
                        "is_closed": 0
                    },
                    {
                        "id": 7,
                        "store_id": 99999,
                        "type": 0,
                        "custom_label": null,
                        "dow": 6,
                        "single_day": null,
                        "closed": 0,
                        "start_time": 700,
                        "end_time": 2000,
                        "group": 1,
                        "is_closed": 0
                    }
                ],
                "pickup_time_slots": {
                    "time": null,
                    "days": null
                },
                "store_vat_details": {
                    "id": 99999,
                    "uses_vat": 0,
                    "sales_tax_label": ""
                },
                "acquisition_options": {
                    "bopis": 1,
                    "sdd": 1,
                    "ropis": 1
                }
            }
        },
        "delivery_options": null,
        "cart_hash_jwt": "...",
        "session": {
            "id": "..."
        }
    },
    "msg": ""
}

Unsuccessful Cart Response (HTTP Response == 200, success: false):

{
    "success": false,
    "data": {
				"...",
        "cart_hash": "W9K",
        "cart_content": {
            "cart_error": "Sorry, this purchase option is no longer available at this store.",
        },
        "cart_hash_jwt": "...",
        "session": {
            "id": "..."
        }
    },
    "msg": ""
}

Cart errors will return an HTTP response code of 200 but success will be false. When encountering errors specific to the cart, the response will include an explanation in data.cart_content.cart_error.

Other Unsuccessful Response (HTTP Response >= 400, success: false):

{
    "success": false,
    "data": {
        "session": {
            "id": "..."
        }
    },
    "msg": "Undefined offset: 1",
    "error_code": 0
}

Non cart errors will return an HTTP response code larger or equal to 400 and success will be false. An error description is provided in the msg when possible.

Retrieving an existing cart

When creating a cart for the first time, note the following:

  • data.session.id (session ID)
  • data.cart_hash_jwt (JSON Web Token or JWT).

The session ID can be redeemed at most endpoints and is useful for persisting a user's session between API calls; e.g. a cart or geographic location. The session ID can expire. Each endpoint payload will contain a session ID. If a session ID is passed to an endpoint and it is not expired, the same ID will be returned. If it is expired, a new ID will be returned.

The JWT uniquely identifies a user's cart and does not expire. It can be redeemed at cart endpoints to retrieve or update an existing cart and complete and order. It is optional, but recommended if long term persistence is required.

Request:

curl 'https://www.locally.com/headless/api/1.0/cart' \
  --header 'Locally-Api-Token: {{API_TOKEN}}' \
  --header 'Locally-Api-Session-Id: {{SESSION_ID}}' \
  --header 'Locally-Pl-Jwt: {{CART_HASH_JWT}}'

Successful Response:

A standard cart payload will be returned. Please review an example of a successful response, if needed.

Unsuccessful Response (HTTP Response == 404, success: false):

{
    "success": false,
    "data": {
        "session": {
            "id": "..."
        }
    },
    "msg": "Authentication error: User not found.",
    "error_code": 1206
}

When a cart cannot be found, an HTTP 404 response with error_code 1206 and a msg string will be returned. success will be false.

Updating a cart

By utilizing a session ID or JWT that belongs to an existing cart, we can update the quantity for items in the cart. Read more about a cart's session ID and JWT.

We can update the quantity for a specific item in the cart by specifying its ID along with the desired quantity. You can also remove an item by specifying the ID to remove. You can find the item's ID by looking for data.cart_content.cart.items[].id in a successful cart response.

Updating cart quantity

Request:

curl 'https://www.locally.com/headless/api/1.0/cart?qty[{{ITEM_ID}}]={{QUANTITY}}&host_domain={{HOST_DOMAIN}}' \
  -X 'POST' \
  --header 'Locally-Api-Token: {{API_TOKEN}}' \
  --header 'Locally-Api-Session-Id: {{SESSION_ID}}' \
  --header 'Locally-Pl-Jwt: {{CART_HASH_JWT}}'

Successful Response:

A standard cart payload will be returned. Please review an example of a successful response, if needed.

Unsuccessful Response:

Please review an example of unsuccessful responses, if needed.

Removing an item from the cart

Request:

curl 'https://www.locally.com/headless/api/1.0/cart?remove_item={{ITEM_ID}}&host_domain={{HOST_DOMAIN}}' \
  -X 'POST' \
  --header 'Locally-Api-Token: {{API_TOKEN}}' \
  --header 'Locally-Api-Session-Id: {{SESSION_ID}}' \
  --header 'Locally-Pl-Jwt: {{CART_HASH_JWT}}'

Successful Response:

A standard cart payload will be returned. Please review an example of a successful response, if needed.

Unsuccessful Response:

Please review an example of unsuccessful responses, if needed.

Creating a BOPIS order

BOPIS orders require a reference to the customer's cart along with their email, name, marketing consent, address information and a Stripe setup intent ID.

The Stripe setup intent ID is constructed by retrieving the store's Stripe Connected ID and combining it with the market place secret key. Please contact Locally customer service for more information on obtaining a market place secret key during a custom integration.

All request types require the unique cart identifier. You can find the cart hash by looking for data.cart_hash in a successful cart response. Please review an example of a successful response, if needed.

BOPIS Request:

curl 'https://www.locally.com/headless/api/1.0/cart/order?hash={{CART_HASH}}&first_name={{FIRST_NAME}}&last_name={{LAST_NAME}}&full_name={{FIRST_NAME}}+{{LAST_NAME}}&email={{EMAIL}}&phone={{PHONE}}}&address={{ADDRESS}}&city={{CITY}}&state={{STATE}}&zip={{ZIP}}&country={{COUNTRY_CODE}}&is_opt_in_consent={{CONSENT_RETAILER}}&is_opt_in_consent_brand={{CONSENT_BRAND}}&setup_intent_id={{SETUP_INTENT_ID}}' \
  -X 'POST' \
  --header 'Locally-Api-Token: {{API_TOKEN}}' \
  --header 'Locally-Api-Session-Id: {{SESSION_ID}}' \
  --header 'Locally-Pl-Jwt: {{CART_HASH_JWT}}'

Successful BOPIS Response (HTTP Response == 200, success: true):

{
    "success": true,
    "data": {
        "message": "Your order has been placed",
        "target_uri": "/order/.../...",
        "status_code": 200,
        "session": {
            "id": "..."
        }
    },
    "msg": ""
}

Unsuccessful Response (HTTP Response >= 400, success: false):

{
    "success": false,
    "data": {
        "session": {
            "id": "..."
        }
    },
    "msg": "We're sorry, we've detected that this item is no longer available and cannot be purchased.",
    "error_code": 401
}

Any error will result in a non-200 HTTP response code. If possible, there will be an error description in the msg field. success will be false.

Creating a ROPIS order

ROPIS orders require a reference to the customer's cart along with their email, name, phone number and marketing consent.

ROPIS request types require the unique cart identifier. You can find the cart hash by looking for data.cart_hash in a successful cart response. Please review an example of a successful response, if needed.

ROPIS Request

curl 'https://www.locally.com/headless/api/1.0/cart/order?hash={{CART_HASH}}}&full_name={{FIRST_NAME}}+{{LAST_NAME}}&email={{EMAIL}}&phone={{PHONE}}}&is_opt_in_consent={{CONSENT_RETAILER}}&is_opt_in_consent_brand={{CONSENT_BRAND}}' \
  -X 'POST' \
  --header 'Locally-Api-Token: {{API_TOKEN}}' \
  --header 'Locally-Api-Session-Id: {{SESSION_ID}}' \
  --header 'Locally-Pl-Jwt: {{CART_HASH_JWT}}'

Successful ROPIS Response (HTTP Response == 200, success: true):

{
    "success": true,
    "data": {
        "message": "Your hold request has been sent.",
        "target_uri": "/order/.../...",
        "status": 200,
        "status_code": 200,
        "session": {
            "id": "..."
        }
    },
    "msg": ""
}

Unsuccessful Response (HTTP Response >= 400, success: false):

{
    "success": false,
    "data": {
        "session": {
            "id": "..."
        }
    },
    "msg": "We're sorry, we've detected that this item is no longer available and cannot be purchased.",
    "error_code": 401
}

Any error will result in a non-200 HTTP response code. If possible, there will be an error description in the msg field. success will be false.