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.
Before you begin
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.identifieron 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.
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.
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.
Shipper Setup
Carrier configures your company inside Grasshopper.
API User
A dedicated user is created for authentication.
Authenticate
Exchange credentials for an access token.
Create Orders
POST new final-mile orders to the API.
Receive Webhooks
Get lifecycle updates in real time.
Retrieve Assets
Download POD and images on terminal status.
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
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.
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 generationpassword: used as the secret for authentication
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.
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:
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
{
"access_token": "eyJhbGciOiJIUzI1NiIs...",
"refresh_token": "def50200a1b2c3..."
}Using the token
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
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.
Required headers
Content-Type: application/jsonAuthorization: {{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).
| Field | Description |
|---|---|
| Order | |
retailer.identifier | The shipper identifier configured in Step 1. Provided by your carrier; must match exactly. |
service_level | Delivery service type code for the order, e.g. EXP or WG. See the service levels reference for all 13 codes. |
ref_order_number | Your PO or order number. Echoed on every webhook so you can reconcile against your OMS. Not enforced as unique (see below). |
total_insurance_coverage | Total insurance coverage declared for the order, in dollars. |
note | Free-text note attached to the order. Optional; an empty string is accepted. |
| Customer (consignee) | |
customer.company | Consignee company name for business deliveries. For residential consignees, use first_name and last_name instead. |
customer.address.address1 / address2 | Street address lines of the delivery destination. address2 is nullable. |
customer.address.city / state / zip | City, two-letter state code, and ZIP of the delivery destination. |
customer.phone1.number | Primary contact phone for delivery coordination. |
customer.phone2.number | Secondary contact phone. Nullable. |
customer.email | Consignee email address. |
| Line items (line_items[], one entry per item) | |
quantity | Number of units of this SKU. |
weight | Item weight in pounds. |
retail_value | Retail value of the item in dollars. |
cube | Item volume in cubic feet. Nullable when unknown. |
sku | Your SKU for the item. |
category | Item category label, e.g. King Mattress. |
name | Item description as it should appear in Grasshopper and on delivery documents. |
vendor | Vendor or manufacturer name. |
freight_info.is_fob | Freight 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_info | Origin pickup contact for the item: first_name, last_name, address, phone, and email. Expected when is_fob is false. |
dimensions | Item dimensions: height, width, depth, and unit (e.g. inch). |
tracking_number | Inbound tracking number for the item. Nullable. |
ref_order_number | Optional. 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.
| Value | What it means | What 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. |
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.
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.
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]
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, orPATCH(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
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.
| Event | Description |
|---|---|
order_created | Order created in Grasshopper |
order_complete_stop | Order completed (Delivered / Rejected / Delivery Failed / Damaged) |
order_updated | Order information has been updated |
order_status_changed | Order status was changed |
tracking_changed | Tracking entry was added |
notes_added | Note was added to the order |
notes_updated | Note was updated |
delivery_changed | Delivery date was changed |
pickup_date_schedule_changed | Pickup date was changed |
expected_arrival_changed | Expected arrival was changed |
order_estimated_arrival_changed | Estimated arrival time changed |
customer_information_changed | Customer information changed on the order |
service_level_changed | Service level changed |
asset_added | Image was added to the order |
asset_removed | Image was removed from the order |
arrive_on_site | Driver team arrived at the consignee |
Item-level events
| Event | Description |
|---|---|
item_status_changed | Line item status was changed |
item_damaged | Individual line item was marked as damaged |
item_added | Item was added |
item_removed | Item was removed |
item_updated | Item was updated |
item_rejected | Individual line item was rejected |
item_failed_delux | Item 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
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
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
Returns all images and related assets as URLs. Includes POD photos, signatures, damage photos, inspection photos, and more.
Asset type reference
| ID | Type |
|---|---|
0 | General |
1 | Signature [Accept] |
2 | Signature [Reject] |
4 | Delivery Location |
5 | Damage Product |
7 | Accept Product |
11 | Item Inspection |
13 | Bill of Lading |
25 | Arrive on Site |
26 | eSignature POD |
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.
Trigger asset retrieval when you receive a terminal-status webhook event. Store the POD and images locally for customer service, claims, and billing workflows.
Errors & troubleshooting
The API uses standard HTTP status codes. The failures below account for nearly every stuck integration:
| Symptom | Likely cause | Fix |
|---|---|---|
401 on every call | Bearer prefix on the Authorization header, or wrong credentials | Send the raw token: Authorization: {{access_token}}. Verify credentials by logging in to the shipper UI. |
401 after working previously | Access token expired | Refresh via the Refresh token endpoint or re-authenticate, then retry. |
| Order rejected on create | Payload not wrapped in order, or retailer.identifier does not match the configured shipper | Wrap the body in a top-level order object and use the exact identifier your carrier provided. |
| Connection refused / 404 on everything | Wrong base URL | {{url}} is your carrier's environment URL, not grasshopperlabs.io. Confirm staging vs production with your carrier. |
| Duplicate orders appearing | ref_order_number is not unique-enforced | Deduplicate on your side before submission. |
| No webhooks arriving | Subscription not registered, or receiver not publicly reachable | Confirm the carrier registered your topic, method, and URL in the Grasshopper UI. Test your endpoint from the public internet. |
Full error semantics: Errors reference
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_numberdeduplication enforced on your side (Step 4)order_createdwebhook 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
Start in staging at staging.grasshopperlabs.io. When you are ready to move to production, reach out to your Grasshopper team.
Resources
Postman Collection
Live, runnable API documentation with all endpoints, schemas, and examples.
Webhooks Reference
Subscription model, event delivery behavior, payloads, and testing.
Order Statuses
All 30 status IDs split into active, terminal, and temporary states.
Tracking Events
204 event types emitted across the order lifecycle, with search.
Service Levels
13 delivery service types including White Glove, Threshold, and Curbside.
Help Center
Answers, guides, and self-service support.
Stuck on something this guide does not cover? Email support@grasshopperlabs.io or submit a ticket.