Getting Started - Grasshopper Labs API
API Docs v1
Shipper Integration

Get up and running with the Grasshopper API

Everything a shipper needs to integrate with the Grasshopper platform: authentication, creating orders, receiving lifecycle webhooks, and retrieving final delivery assets like POD and images. Six steps, in the order you will actually do them.

Setup · Prerequisites

Before you begin

All testing happens in staging: staging.grasshopperlabs.io

Build and test your integration against the Grasshopper staging environment at staging.grasshopperlabs.io. You can sign up there on your own and obtain a username and password; no carrier involvement is needed to start testing. Move to production only after completing the go-live checklist at the end of this guide.

Grasshopper is a multi-tenant platform. Every carrier (licensee) runs on their own base URL, and your integration connects to their environment, not a shared Grasshopper endpoint. Throughout this guide, {{url}} means the base URL you are working against: https://staging.grasshopperlabs.io while testing, and the carrier's production URL, for example https://carrier-name.grasshopperlabs.io, at go-live.

Production credentials cannot be self-served. The carrier controls your production setup, so collect everything below before go-live.

Request this from your carrier for production

  • Production base URL for their Grasshopper environment
  • Shipper identifier, the value you will send as retailer.identifier on every order
  • API user credentials (email and password) created under your shipper account (Steps 1 and 2)
  • Webhook registration: give the carrier your receiver URL(s), the HTTP method you accept, and the list of topics you want (Step 5). Webhook subscriptions are configured by the carrier inside the Grasshopper UI, not through the API.

One email to your carrier contact covering these four items removes every production blocker in this guide.

Introduction

What you can build

The Grasshopper Shipper Integration lets your system programmatically create and manage orders inside Grasshopper, with full lifecycle visibility from order creation through final delivery. Through a combination of REST APIs and event-driven webhooks, your system can:

  • Create final-mile orders via Grasshopper's external APIs
  • Track orders and individual line items across their full lifecycle
  • Receive real-time updates via webhook events
  • Retrieve POD, signatures, and delivery images once orders reach a terminal status

Who this guide is for

Integration Developers

Building the connection between your OMS/ERP and Grasshopper.

Solution Architects

Designing order flow, lifecycle tracking, and asset retrieval.

Product & Operations

Owning the end-to-end order-to-delivery workflow.

The Integration Lifecycle

Six steps, in order

Every shipper integration follows the same path. Steps 1 and 2 happen inside the Grasshopper UI (carrier-side). Steps 3 through 6 are yours.

1

Shipper Setup

Carrier configures your company inside Grasshopper.

2

API User

A dedicated user is created for authentication.

3

Authenticate

Exchange credentials for an access token.

4

Create Orders

POST new final-mile orders to the API.

5

Receive Webhooks

Get lifecycle updates in real time.

6

Retrieve Assets

Download POD and images on terminal status.

Setup · Carrier-side

1Configure the Shipper in Grasshopper

Before any API integration begins, your company must be created and configured as a shipper inside the Grasshopper UI. This step is performed by the carrier within their Grasshopper environment. You cannot do it through the API; if it has not happened yet, this is the first thing to ask your carrier for.

Information the carrier needs from you

  • Primary contact first and last name
  • Primary contact email address
  • Company name and company address
  • Any additional shipper configuration fields required by the carrier's workflow
Why this matters

This configuration establishes your company as an active entity in Grasshopper and binds your integration to the correct business account. The retailer.identifier you send when creating orders (Step 4) must match this configured shipper. The carrier gives you this value; you do not choose it.

Setup · Carrier-side

2Create an API User

Once your shipper exists in Grasshopper, a dedicated user is created under that shipper to handle API authentication. A valid user profile is required before any API access can begin. The credentials are:

  • email: used as the username for token generation
  • password: used as the secret for authentication
Use a dedicated integration mailbox

The email on this API user also receives system-generated order-creation confirmations and password resets. Use a system mailbox like api-integration@yourcompany.com, never a personal inbox. If the person leaves, your integration should not leave with them.

This user can also log in to the shipper UI, which is useful for validating credentials during testing and visually confirming that orders you create via the API actually landed.

API · Authentication

3Authenticate

Authentication must be completed before any other API call. Exchange your API user credentials for an access token by sending them as application/x-www-form-urlencoded:

POST{{url}}/api/rest/auth
curl --location -g '{{url}}/api/rest/auth' \
  --data-urlencode 'user_name={{username}}' \
  --data-urlencode 'password={{password}}'
// Node.js 18+ (native fetch)
const res = await fetch(`${BASE_URL}/api/rest/auth`, {
  method: "POST",
  headers: { "Content-Type": "application/x-www-form-urlencoded" },
  body: new URLSearchParams({ user_name: USERNAME, password: PASSWORD })
});
const auth = await res.json();
import requests

res = requests.post(
    f"{BASE_URL}/api/rest/auth",
    data={"user_name": USERNAME, "password": PASSWORD},
)
auth = res.json()

Response

200 OK
{
  "access_token": "eyJhbGciOiJIUzI1NiIs...",
  "refresh_token": "def50200a1b2c3..."
}

Using the token

Send the raw token. No Bearer prefix.

Every subsequent request must include the header Authorization: {{access_token}}. This is the raw token value, not Bearer {{access_token}}. Sending a Bearer prefix is the most common cause of 401 errors during integration.

  • Valid credentials: Grasshopper returns the tokens required for subsequent API requests.
  • Invalid credentials: the request is rejected. Do not proceed; verify the username and password by logging in to the shipper UI.
  • Token expiry: when a request returns 401 with a previously working token, refresh it via the Refresh token endpoint or re-authenticate, then retry the request.

Full schema and examples: Postman Authentication Docs · Authentication reference

API · Order Creation

4Create an Order

The Order Creation API creates new final-mile orders in Grasshopper for operational planning, manifest assignment, and downstream delivery execution. The request body wraps everything in a top-level order object.

POST{{url}}/api/orders

Required headers

  • Content-Type: application/json
  • Authorization: {{access_token}} (raw token, no Bearer prefix)
curl --location -g '{{url}}/api/orders' \
  --header 'Content-Type: application/json' \
  --header 'Authorization: {{access_token}}' \
  --data-raw '{
    "order": {
      "retailer": { "identifier": "adouglas" },
      "service_level": "EXP",
      "ref_order_number": "19007365728-EX-M760391-R",
      "customer": {
        "company": "Acme Sleep Co.",
        "address": {
          "address1": "2111 Commerce Way",
          "address2": null,
          "zip": "84663",
          "city": "Springville",
          "state": "UT"
        },
        "phone1": { "number": "1111111111" },
        "phone2": { "number": null },
        "email": "customer@email.com"
      },
      "total_insurance_coverage": 4580,
      "line_items": [
        {
          "quantity": 1,
          "weight": 0,
          "retail_value": 450,
          "cube": null,
          "sku": "7055-50",
          "category": "King Mattress",
          "name": "Classic Hybrid Mattress, King : 11",
          "vendor": "Acme Sleep",
          "freight_info": {
            "is_fob": false,
            "vendor_info": {
              "first_name": "Sam",
              "last_name": "Reeves",
              "address": {
                "address1": "861 Warehouse Pkwy",
                "address2": null,
                "city": "Spring Creek",
                "state": "NV",
                "zip": "89815"
              },
              "phone": "7755550172",
              "email": "pickup@vendor.com"
            }
          },
          "dimensions": {
            "height": 90,
            "width": 50,
            "depth": 80,
            "unit": "inch"
          },
          "tracking_number": null,
          "ref_order_number": "line-item-po-number"
        }
      ],
      "note": ""
    }
  }'
const payload = {
  order: {
    retailer: { identifier: "adouglas" },
    service_level: "EXP",
    ref_order_number: "19007365728-EX-M760391-R",
    customer: {
      company: "Acme Sleep Co.",
      address: { address1: "2111 Commerce Way", address2: null, city: "Springville", state: "UT", zip: "84663" },
      phone1: { number: "1111111111" },
      phone2: { number: null },
      email: "customer@email.com"
    },
    total_insurance_coverage: 4580,
    line_items: [
      {
        quantity: 1,
        weight: 0,
        retail_value: 450,
        cube: null,
        sku: "7055-50",
        category: "King Mattress",
        name: "Classic Hybrid Mattress, King : 11",
        vendor: "Acme Sleep",
        freight_info: {
          is_fob: false,
          vendor_info: {
            first_name: "Sam",
            last_name: "Reeves",
            address: { address1: "861 Warehouse Pkwy", address2: null, city: "Spring Creek", state: "NV", zip: "89815" },
            phone: "7755550172",
            email: "pickup@vendor.com"
          }
        },
        dimensions: { height: 90, width: 50, depth: 80, unit: "inch" },
        tracking_number: null,
        ref_order_number: "line-item-po-number"  // optional
      }
    ],
    note: ""
  }
};

const res = await fetch(`${BASE_URL}/api/orders`, {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
    "Authorization": accessToken  // raw token, no Bearer prefix
  },
  body: JSON.stringify(payload)
});
const created = await res.json();
import requests

payload = {
    "order": {
        "retailer": {"identifier": "adouglas"},
        "service_level": "EXP",
        "ref_order_number": "19007365728-EX-M760391-R",
        "customer": {
            "company": "Acme Sleep Co.",
            "address": {"address1": "2111 Commerce Way", "address2": None, "city": "Springville", "state": "UT", "zip": "84663"},
            "phone1": {"number": "1111111111"},
            "phone2": {"number": None},
            "email": "customer@email.com",
        },
        "total_insurance_coverage": 4580,
        "line_items": [
            {
                "quantity": 1,
                "weight": 0,
                "retail_value": 450,
                "cube": None,
                "sku": "7055-50",
                "category": "King Mattress",
                "name": "Classic Hybrid Mattress, King : 11",
                "vendor": "Acme Sleep",
                "freight_info": {
                    "is_fob": False,
                    "vendor_info": {
                        "first_name": "Sam",
                        "last_name": "Reeves",
                        "address": {"address1": "861 Warehouse Pkwy", "address2": None, "city": "Spring Creek", "state": "NV", "zip": "89815"},
                        "phone": "7755550172",
                        "email": "pickup@vendor.com",
                    },
                },
                "dimensions": {"height": 90, "width": 50, "depth": 80, "unit": "inch"},
                "tracking_number": None,
                "ref_order_number": "line-item-po-number",  # optional
            }
        ],
        "note": "",
    }
}

res = requests.post(
    f"{BASE_URL}/api/orders",
    json=payload,
    headers={"Authorization": ACCESS_TOKEN},  # raw token, no Bearer prefix
)
created = res.json()

The example uses service_level: "EXP". All 13 service level codes, including White Glove, Threshold, and Curbside, are listed in the service levels reference.

Field reference

Every field in the create-order body. Nullable fields may be sent as null; optional fields may be omitted entirely. Each line item gets its own independent lifecycle and status (see Step 5).

FieldDescription
Order
retailer.identifierThe shipper identifier configured in Step 1. Provided by your carrier; must match exactly.
service_levelDelivery service type code for the order, e.g. EXP or WG. See the service levels reference for all 13 codes.
ref_order_numberYour PO or order number. Echoed on every webhook so you can reconcile against your OMS. Not enforced as unique (see below).
total_insurance_coverageTotal insurance coverage declared for the order, in dollars.
noteFree-text note attached to the order. Optional; an empty string is accepted.
Customer (consignee)
customer.companyConsignee company name for business deliveries. For residential consignees, use first_name and last_name instead.
customer.address.address1 / address2Street address lines of the delivery destination. address2 is nullable.
customer.address.city / state / zipCity, two-letter state code, and ZIP of the delivery destination.
customer.phone1.numberPrimary contact phone for delivery coordination.
customer.phone2.numberSecondary contact phone. Nullable.
customer.emailConsignee email address.
Line items (line_items[], one entry per item)
quantityNumber of units of this SKU.
weightItem weight in pounds.
retail_valueRetail value of the item in dollars.
cubeItem volume in cubic feet. Nullable when unknown.
skuYour SKU for the item.
categoryItem category label, e.g. King Mattress.
nameItem description as it should appear in Grasshopper and on delivery documents.
vendorVendor or manufacturer name.
freight_info.is_fobFreight scope flag. true means last-mile delivery only; false means the carrier also handles vendor pickup and vendor_info must be filled. See Freight scope below.
freight_info.vendor_infoOrigin pickup contact for the item: first_name, last_name, address, phone, and email. Expected when is_fob is false.
dimensionsItem dimensions: height, width, depth, and unit (e.g. inch).
tracking_numberInbound tracking number for the item. Nullable.
ref_order_numberOptional. PO number for this individual line item, when it differs from the order-level number.

Freight scope: what is_fob controls

The freight_info.is_fob flag on each line item tells the carrier how much of the journey they are responsible for. This is set per item, not per order, so a single order can mix both modes.

ValueWhat it meansWhat you must send
true Last-mile delivery only. The item arrives at the carrier's facility on its own; the carrier is responsible only for the final-mile delivery to the consignee. vendor_info is not required.
false Pickup plus delivery. The carrier is also expected to pick the item up at the vendor's location before the final-mile delivery. Fill vendor_info completely: first_name, last_name, address, phone, and email of the pickup contact.
If is_fob is false, fill in vendor_info

Sending is_fob: false without a complete vendor_info leaves the carrier with a pickup obligation and no origin details. The example above sets is_fob: false, which is why vendor_info is fully populated.

PO number uniqueness is not enforced

Grasshopper does not enforce ref_order_number as unique. If you require deduplication, enforce it on your side before submission. Submitting the same PO twice creates two orders.

Confirmation pattern

Treat the order_created webhook event as the confirmation that an order was accepted. Subscribe to it (Step 5) and use it to reconcile orders on your side, rather than relying only on the synchronous API response.

Full schema and live examples: POST Create New Order [Final Mile Only]

Events · Real-Time Lifecycle Tracking

5Receive Lifecycle Webhooks

How registration works

Webhook subscriptions are configured by the carrier inside the Grasshopper UI. There is no self-serve subscription API. To get connected, send your carrier three things for each subscription:

Send your carrier

  • Topic name: one of the supported topics listed below
  • Method: POST, PUT, or PATCH (whichever your endpoint accepts)
  • URL: your publicly reachable endpoint that will receive the event payload

Register your staging receiver first, verify events arrive, then register production URLs during go-live.

Order vs. item status

Grasshopper tracks status at two levels and emits webhook events at both, so your system always has an accurate picture of where each order and each item is in its lifecycle:

  • Item-level status: each individual line item has its own independent lifecycle
  • Order-level status: an aggregated view; the order reflects the least advanced state across its items
Example

An order has 5 items. Four are Received and one is still Pending Arrival. The order status remains Pending Arrival. Only when all 5 items reach Received does the order transition.

Order-level events

All 23 supported topics are listed below. The same list with payload details lives on the Webhook Topics page.

EventDescription
order_createdOrder created in Grasshopper
order_complete_stopOrder completed (Delivered / Rejected / Delivery Failed / Damaged)
order_updatedOrder information has been updated
order_status_changedOrder status was changed
tracking_changedTracking entry was added
notes_addedNote was added to the order
notes_updatedNote was updated
delivery_changedDelivery date was changed
pickup_date_schedule_changedPickup date was changed
expected_arrival_changedExpected arrival was changed
order_estimated_arrival_changedEstimated arrival time changed
customer_information_changedCustomer information changed on the order
service_level_changedService level changed
asset_addedImage was added to the order
asset_removedImage was removed from the order
arrive_on_siteDriver team arrived at the consignee

Item-level events

EventDescription
item_status_changedLine item status was changed
item_damagedIndividual line item was marked as damaged
item_addedItem was added
item_removedItem was removed
item_updatedItem was updated
item_rejectedIndividual line item was rejected
item_failed_deluxItem did not pass deluxe process

Building your receiver

  • Respond fast with a 2xx. Acknowledge receipt immediately, then process the event asynchronously. Slow responses look like failures and trigger retries.
  • Be idempotent. Events are asynchronous, may be retried, and may arrive more than once for the same lifecycle change. Deduplicate on the event content plus your ref_order_number.
  • Tolerate out-of-order delivery. Do not assume events arrive in lifecycle sequence. When in doubt, re-fetch the order via Retrieve order for current state.
  • Tolerate unknown fields. Payloads may gain fields over time; parse defensively.

For full setup details, payload structure, and testing, see the Webhooks Overview · Payload · Topics · Testing

API · Final Delivery Artifacts

6Retrieve POD & Delivery Assets

Once an order reaches a terminal status, you can retrieve final delivery artifacts including Proof of Delivery (POD), signatures, and delivery images.

Terminal statuses

DELIVEREDCUSTOMER_PICKUPCANCELLEDREJECTEDDAMAGEDRETURNEDDELIVERY_FAILEDDISPOSEDMOVED_TO_GIMSPICKUP_FAILED

Download POD

POST{{url}}/api/orders/{order_id}/download_pod

Returns the POD document as a binary file stream. Use after the order reaches a terminal status, typically DELIVERED.

Download images & related assets

GET{{url}}/orders/{order_id}/images

Returns all images and related assets as URLs. Includes POD photos, signatures, damage photos, inspection photos, and more.

Asset type reference

IDType
0General
1Signature [Accept]
2Signature [Reject]
4Delivery Location
5Damage Product
7Accept Product
11Item Inspection
13Bill of Lading
25Arrive on Site
26eSignature POD
Asset availability & URL expiry

Assets may not be available immediately when the terminal-status webhook fires. Allow a short delay and implement retry logic. Asset URLs are valid for 7 days. If you need long-term retention, download and persist the assets on your side.

Recommended pattern

Trigger asset retrieval when you receive a terminal-status webhook event. Store the POD and images locally for customer service, claims, and billing workflows.

Troubleshooting

Errors & troubleshooting

The API uses standard HTTP status codes. The failures below account for nearly every stuck integration:

SymptomLikely causeFix
401 on every callBearer prefix on the Authorization header, or wrong credentialsSend the raw token: Authorization: {{access_token}}. Verify credentials by logging in to the shipper UI.
401 after working previouslyAccess token expiredRefresh via the Refresh token endpoint or re-authenticate, then retry.
Order rejected on createPayload not wrapped in order, or retailer.identifier does not match the configured shipperWrap the body in a top-level order object and use the exact identifier your carrier provided.
Connection refused / 404 on everythingWrong base URL{{url}} is your carrier's environment URL, not grasshopperlabs.io. Confirm staging vs production with your carrier.
Duplicate orders appearingref_order_number is not unique-enforcedDeduplicate on your side before submission.
No webhooks arrivingSubscription not registered, or receiver not publicly reachableConfirm the carrier registered your topic, method, and URL in the Grasshopper UI. Test your endpoint from the public internet.

Full error semantics: Errors reference

Launch

Go-live checklist

Run this end to end in staging before requesting production access. Every item maps to a step above.

  • Staging account created at staging.grasshopperlabs.io; authentication returns tokens (Step 3)
  • Test order created via API and visible in the shipper UI (Step 4)
  • ref_order_number deduplication enforced on your side (Step 4)
  • order_created webhook received and reconciled against your OMS (Step 5)
  • Receiver returns 2xx quickly, processes asynchronously, and handles duplicate events (Step 5)
  • Terminal-status flow tested: POD downloaded, images retrieved and persisted before the 7-day URL expiry (Step 6)
  • Token expiry and 401 recovery tested (Step 3)
  • Production base URL, credentials, and webhook registrations confirmed with your carrier
You're ready to integrate

Start in staging at staging.grasshopperlabs.io. When you are ready to move to production, reach out to your Grasshopper team.

Next Steps

Resources

Stuck on something this guide does not cover? Email support@grasshopperlabs.io or submit a ticket.