You're viewing the public API reference. To generate an API key and start making requests, sign in to your Obermeyer Dropship account.
Sign inObermeyer Dropship Platform
A REST API for submitting drop-ship orders, browsing live inventory, and receiving push notifications when orders are accepted, shipped, or fail. All requests are authenticated with a bearer token; all responses are JSON unless otherwise noted.
Introduction
Quick start in three steps
1. Generate an API key
Go to Settings → API Keys and click Create Key. The raw token is shown exactly once at creation — copy it into a password manager or secret store immediately.
2. Validate your line items
Hit
POST /api/orders/validatewith your line items to confirm UPCs match Obermeyer's inventory before you ever submit an order. Catches typos, missing barcodes, and out-of-stock SKUs.3. Submit the order
POST /api/orderswith the customer PO, ship-to address, and lines. You'll get a201with the platform order id; subsequent state changes (accepted, shipped, error) come back via webhook events or by pollingGET /api/orders/:id.
Authentication
Bearer tokens for all requests
Every request must include an Authorization header containing your API key. Keys are scoped to the user who created them and inherit that user's customer code — orders submitted with the key are automatically associated with your account.
Authorization: Bearer obey_live_a1b2c3d4e5f6...- Keys begin with the prefix
obey_live_. - Keys are stored only as a SHA-256 hash. If you lose a key, regenerate — we cannot recover it.
- Revoke a key at any time from Settings → API Keys — revoked keys 401 immediately.
- Sessions cookies (browser) also work for the same endpoints if you're calling from a logged-in tab.
Conventions & errors
Status codes, error shape, dates
HTTP status codes
- 200OKSuccessful read.
- 201CreatedOrder created and relayed to ERP.
- 400Bad RequestValidation failed — missing fields, unknown UPC, malformed JSON. Body contains
detailswhen applicable. - 401UnauthorizedMissing, malformed, or revoked API key.
- 403ForbiddenAuthenticated, but the resource isn't yours.
- 404Not FoundResource does not exist or is owned by another account.
- 409ConflictDuplicate PO — same
customer_powithin the last 24 hours. Body includesexistingOrder. - 502Bad GatewayOrder saved on our side but the downstream relay to the ERP failed. Order lands in the error queue; safe to retry from the dashboard.
- 500Server ErrorUnexpected — please retry. Persistent 500s are bugs; report them.
Error response shape
{
"error": "Order lines missing or unrecognized barcodes",
"details": [
{
"style": "21104",
"color": "BLK",
"size": "M",
"sku_composite": "21104-BLK-M",
"requested": 2,
"upc": null,
"available": 0,
"status": "missing_upc",
"warning": true,
"candidates": [
{ "upc": "888555971149", "size": "S", "sku_composite": "21104-BLK-S", "available": 12 },
{ "upc": "888555971156", "size": "M", "sku_composite": "21104-BLK-M", "available": 4 },
{ "upc": "888555971163", "size": "L", "sku_composite": "21104-BLK-L", "available": 0 }
]
}
]
}Other conventions
- All timestamps are ISO 8601 in UTC (
2026-04-21T15:08:22.000Z). - All money values are decimal strings or numbers in USD.
- Order ids are zero-padded sequential strings prefixed with
DS-(e.g.DS-001234) — give this number to your Obermeyer rep when calling about an order. Other resources (shipments, events) use lowercase v4 UUIDs. - Pagination uses
page/limitquery params; maxlimitis 100.
Orders endpoints
Create, list, retrieve, upload, and validate orders
/api/orders·API keyCreate a single drop-ship order with one or more line items. The order is validated against live inventory, persisted, and immediately relayed to the ERP.
Request body
| Field | Type | Required | Description |
|---|---|---|---|
| customer_po | string | Required | Your purchase-order reference. Must be unique per customer per 24-hour window. |
| lines | OrderLine[] | Required | At least one line item. See OrderLine below. |
| ship_name | string | Optional | Recipient name on the shipping label. |
| address1 | string | Optional | Street address line 1. |
| address2 | string | Optional | Street address line 2 (suite, apt, etc). |
| city | string | Optional | Destination city. |
| state | string | Optional | Two-letter state or province code. |
| zip | string | Optional | Postal code. |
| country | string | Optional | ISO country code. Defaults to "US". |
| phone | string | Optional | Recipient phone number for delivery. |
| string | Optional | Recipient email for delivery notifications. | |
| contact | string | Optional | Contact name at the destination. |
| start_ship_date | date (YYYY-MM-DD) | Optional | Earliest acceptable ship date. |
| comp_ship_date | date (YYYY-MM-DD) | Optional | Cancel-by / completion ship date. |
| special_instructions | string | Optional | Free-text delivery notes (max 500 chars). |
| routing | string | Optional | Carrier routing instructions, if you have a preferred carrier on file. |
| order_notes | string | Optional | Internal notes — visible to admins only. |
OrderLine
| Field | Type | Required | Description |
|---|---|---|---|
| style | string | Required | Obermeyer style number (e.g. 21104). |
| color | string | Required | Color code (e.g. BLK). |
| size | string | Required | Size code (e.g. M). |
| quantity | integer | Required | Units ordered (≥ 1). |
| upc | string | Required | 12-digit barcode. Must match an active SKU in Obermeyer inventory. |
| season | string | Optional | Optional season tag (e.g. "2025-26"). |
| style_description | string | Optional | Optional description for your records. |
curl -X POST https://dropship.obermeyer.com/api/orders \
-H "Authorization: Bearer obey_live_YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{
"customer_po": "PO-12345",
"ship_name": "Aspen Mountain Sports",
"address1": "123 Main St",
"city": "Aspen",
"state": "CO",
"zip": "81611",
"country": "US",
"phone": "970-555-0142",
"email": "ops@example.com",
"lines": [
{
"style": "21104",
"color": "BLK",
"size": "M",
"quantity": 2,
"upc": "888555971156"
}
]
}'{
"order": {
"id": "DS-001234",
"customer_code": "wick123",
"customer_po": "PO-12345",
"status": "submitted",
"submission_method": "api",
"ship_name": "Aspen Mountain Sports",
"address1": "123 Main St",
"city": "Aspen",
"state": "CO",
"zip": "81611",
"country": "US",
"submitted_at": "2026-04-21T15:07:52.791Z",
"created_at": "2026-04-21T15:07:52.791Z",
"order_total": 100,
"lines": [
{
"id": "5a14...",
"line_number": 1,
"style": "21104",
"color": "BLK",
"size": "M",
"quantity": 2,
"sell_price": "50",
"upc": "888555971156",
"sku_composite": "21104-BLK-M"
}
]
}
}Pricing
Prices are calculated on our side using your account's active terms (set by your sales rep) and returned in the response on every line and as order_total. Any sell_price sent in the request body is ignored. See the price list endpoint below to fetch your current prices ahead of time.
Notable error responses
- 400Missing/unknown UPC — body includes
details[].candidateswith valid barcodes for that style + color so you can self-correct. - 409Duplicate PO — body includes
existingOrderwith the prior order id and status. - 409No pricing terms — your account has no active pricing terms. Contact your sales rep.
- 422Product has no price — a UPC in the request has no published price yet. Body includes the offending
upc. - 502Make.com relay failed — body includes the saved
orderplusmakeErrorandmakeStatus. Order is in the error queue and can be retried from the dashboard.
/api/orders·API keyList orders for your account, newest first. Customer accounts only see their own orders.
Query parameters
| Field | Type | Required | Description |
|---|---|---|---|
| page | integer | Optional | 1-indexed page number. Default 1. |
| limit | integer | Optional | Page size. Default 20, max 100. |
| status | string | Optional | Filter by status (e.g. submitted, accepted, shipped, error). |
| search | string | Optional | Substring search on customer_po (case-insensitive). |
curl "https://dropship.obermeyer.com/api/orders?status=accepted&limit=20" \
-H "Authorization: Bearer obey_live_YOUR_KEY"{
"orders": [
{
"id": "DS-001234",
"customer_po": "PO-12345",
"customer_code": "wick123",
"status": "accepted",
"erp_order_number": "1048392",
"accepted_at": "2026-04-21T15:08:22.000Z",
"created_at": "2026-04-21T15:07:52.791Z",
"_count": { "lines": 1 }
}
],
"pagination": {
"page": 1,
"limit": 20,
"total": 47,
"totalPages": 3
}
}/api/orders/{id}·API keyRetrieve a single order with its line items, shipments, and event timeline.
Path parameters
| Field | Type | Required | Description |
|---|---|---|---|
| id | string | Required | The platform order id returned from order creation, e.g. DS-001234. |
curl https://dropship.obermeyer.com/api/orders/DS-001234 \
-H "Authorization: Bearer obey_live_YOUR_KEY"{
"order": {
"id": "DS-001234",
"customer_po": "PO-12345",
"status": "shipped",
"erp_order_number": "1048392",
"accepted_at": "2026-04-21T15:08:22.000Z",
"shipped_at": "2026-04-23T16:00:00.000Z",
"lines": [ /* OrderLine[] */ ],
"shipments": [
{
"id": "a2b8f501-7e4c-49aa-b3a7-d2f1e0cbd6a9",
"trackingNumber": "1Z999AA10123456784",
"carrier": "UPS",
"shipDate": "2026-04-23T16:00:00.000Z",
"lines": [
{ "style": "21104", "color": "BLK", "size": "M", "qty": 2 }
]
}
],
"events": [
{
"type": "shipped",
"fromStatus": "accepted",
"toStatus": "shipped",
"actorRole": "system",
"actorLabel": "Make.com webhook",
"message": "Shipment recorded via UPS (1Z999AA10123456784) — order fully shipped",
"createdAt": "2026-04-23T16:00:00.000Z"
}
]
}
}/api/orders/upload·API keySubmit an order whose line items come from a CSV or XLSX file. Header fields are sent alongside the file as multipart form fields.
Multipart form fields
| Field | Type | Required | Description |
|---|---|---|---|
| file | file | Required | CSV or XLSX file. See the CSV format section for required columns. |
| customer_po | string | Required | Your purchase-order reference. |
| ship_name | string | Optional | Recipient name. |
| address1, address2, city, state, zip | string | Optional | Destination address fields. |
| country | string | Optional | ISO country code. Defaults to "US". |
| phone, email | string | Optional | Delivery contact info. |
| special_instructions | string | Optional | Free-text notes. |
| start_ship_date, comp_ship_date | date | Optional | Shipping window. |
curl -X POST https://dropship.obermeyer.com/api/orders/upload \
-H "Authorization: Bearer obey_live_YOUR_KEY" \
-F "file=@order.csv" \
-F "customer_po=PO-12345" \
-F "ship_name=Aspen Mountain Sports" \
-F "address1=123 Main St" \
-F "city=Aspen" \
-F "state=CO" \
-F "zip=81611"{
"order": {
"id": "DS-001234",
"submission_method": "csv_upload",
"status": "submitted",
"lines": [ /* one OrderLine per CSV row */ ]
},
"parseWarnings": [
{ "row": 4, "message": "Skipped — missing required column 'qty'" }
]
}parseWarnings is only present when some rows were skipped due to malformed data. Rows that parse successfully are submitted even when others are skipped.
/api/orders/validate·API keyDry-run the inventory check that /api/orders performs at submit time. No order is created. Use this to surface UPC errors and stock warnings in your UI before the customer commits.
Request body
| Field | Type | Required | Description |
|---|---|---|---|
| lines | OrderLine[] | Required | Same shape as in /api/orders. Only style, color, size, quantity, upc are inspected. |
curl -X POST https://dropship.obermeyer.com/api/orders/validate \
-H "Authorization: Bearer obey_live_YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{
"lines": [
{ "style": "21104", "color": "BLK", "size": "M", "quantity": 2, "upc": "888555971156" },
{ "style": "21104", "color": "BLK", "size": "L", "quantity": 999 }
]
}'{
"validation": [
{
"style": "21104", "color": "BLK", "size": "M",
"sku_composite": "21104-BLK-M",
"requested": 2, "available": 12,
"upc": "888555971156",
"status": "ok",
"warning": false
},
{
"style": "21104", "color": "BLK", "size": "L",
"sku_composite": "21104-BLK-L",
"requested": 999, "available": 0,
"upc": null,
"status": "missing_upc",
"warning": true,
"candidates": [
{ "upc": "888555971163", "size": "L", "sku_composite": "21104-BLK-L", "available": 0 }
]
}
]
}Status values
ok— UPC matched and stock is sufficient.insufficient— UPC matched but ordered qty exceeds available;/api/orderswill still accept the order with a warning recorded.missing_upc— line had noupcfield. Hard fail at submit time.unknown_upc— UPC was provided but doesn't match any active inventory record. Hard fail at submit time.
Inventory endpoints
Search live stock and bulk-validate baskets
/api/inventory·API keyBrowse current stock. Customers only see SKUs that have been matched between Shopify and the warehouse feed (no internal-only or shopify-only rows).
Query parameters
| Field | Type | Required | Description |
|---|---|---|---|
| style | string | Optional | Substring match on style code (case-insensitive). |
| color | string | Optional | Substring match on color code. |
| size | string | Optional | Substring match on size code. |
| page | integer | Optional | Default 1. Ignored when all=1. |
| limit | integer | Optional | Default 50, max 100. Ignored when all=1. |
| all | boolean | Optional | Pass "1" to return every match in one response. Recommended for nightly catalog sync. |
curl "https://dropship.obermeyer.com/api/inventory?style=21104&page=1&limit=50" \
-H "Authorization: Bearer obey_live_YOUR_KEY"curl "https://dropship.obermeyer.com/api/inventory?all=1" \
-H "Authorization: Bearer obey_live_YOUR_KEY"{
"items": [
{
"style": "21104",
"color": "BLK",
"size": "M",
"skuComposite": "21104-BLK-M",
"upc": "888555971156",
"availableQty": 12,
"status": "matched",
"msrp": 159,
"yourPrice": 80,
"lastFeedAt": "2026-04-29T13:00:00.000Z"
}
],
"page": 1,
"limit": 50,
"total": 1248,
"totalPages": 25,
"lastFeedAt": "2026-04-29T13:00:00.000Z",
"pricingTerms": { "formula_type": "pct_off_msrp", "value": 50 }
}yourPriceis calculated using your account's active pricing terms and rounded up to the nearest dollar. If your account has no active terms, yourPrice is null and orders will be rejected — contact your sales rep to set up pricing.
lastFeedAtat the top level reflects the most recent inventory sync run. Use it to display a “data current as of” timestamp in your UI.
/api/inventory/check·API keyBulk-check a list of items against current stock without creating an order. Convenient for AJAX cart validation.
Query parameters
| Field | Type | Required | Description |
|---|---|---|---|
| items | JSON-encoded array | Required | URL-encoded JSON array of { style, color, size, quantity, upc? } objects. |
ITEMS='[{"style":"21104","color":"BLK","size":"M","quantity":2,"upc":"888555971156"}]'
curl -G https://dropship.obermeyer.com/api/inventory/check \
--data-urlencode "items=$ITEMS" \
-H "Authorization: Bearer obey_live_YOUR_KEY"{
"results": [
{
"style": "21104", "color": "BLK", "size": "M",
"sku_composite": "21104-BLK-M",
"requested": 2, "available": 12,
"upc": "888555971156",
"status": "ok",
"warning": false
}
],
"allSufficient": true
}Pricing endpoints
Fetch your account-specific prices for every product
/api/pricing·API keyReturns the full price list for your account: one row per matched UPC with MSRP and your calculated price. Pass format=csv to download the same data as a CSV attachment.
Query parameters
| Field | Type | Required | Description |
|---|---|---|---|
| format | string | Optional | Pass "csv" for a downloadable CSV. Default returns JSON. |
curl https://dropship.obermeyer.com/api/pricing \
-H "Authorization: Bearer obey_live_YOUR_KEY"curl https://dropship.obermeyer.com/api/pricing?format=csv \
-H "Authorization: Bearer obey_live_YOUR_KEY" \
-o pricelist.csv{
"customer_code": "WICK001",
"terms": {
"formula_type": "pct_off_msrp",
"value": 50,
"description": "50% off MSRP"
},
"count": 1248,
"items": [
{
"style": "21104",
"color": "BLK",
"size": "M",
"sku": "21104-BLK-M",
"upc": "888555971156",
"msrp": 159,
"your_price": 80,
"available_qty": 12
}
]
}Notable error responses
- 409No pricing terms — your account has no active terms. Contact your sales rep.
/api/pricing/quote·API keyQuote a basket of UPCs without creating an order. Returns unit price, line totals, and order total. Useful for showing live cart subtotals in your own UI.
curl -X POST https://dropship.obermeyer.com/api/pricing/quote \
-H "Authorization: Bearer obey_live_YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{
"lines": [
{ "upc": "888555971156", "quantity": 2 },
{ "upc": "888555971163", "quantity": 1 }
]
}'{
"items": [
{ "upc": "888555971156", "unit_price": 80, "msrp": 159, "quantity": 2, "line_total": 160 },
{ "upc": "888555971163", "unit_price": 80, "msrp": 159, "quantity": 1, "line_total": 80 }
],
"order_total": 240
}Webhooks
Server-pushed events instead of polling
Register a webhook endpoint from Settings → Webhooks. Pick the events you want, copy the signing secret (shown exactly once), and we'll POST signed JSON to your URL whenever those events fire.
Supported events
order.submittedOrder received and relayed to the ERP. Fires once per order.
order.acceptedERP confirmed the order and assigned an order number.
order.shippedA shipment was recorded. Fires once per shipment row — partial shipments fire multiple times.
order.errorSubmission failed at any stage. Includes error_message.
Request headers
Content-Type: application/json
X-Obey-Event: order.accepted
X-Obey-Signature: sha256=<hex-hmac>
X-Obey-Delivered-At: 2026-04-21T15:08:22.000Z
User-Agent: ObermeyerDropship-Webhook/1.0Envelope
All events share the same outer envelope. The shape inside data varies per event — see below.
{
"event": "order.accepted",
"delivered_at": "2026-04-21T15:08:22.000Z",
"data": { /* event-specific payload */ }
}Event payloads
order.submitted · data
{
"order_id": "DS-001234",
"customer_po": "PO-12345",
"customer_code": "wick123",
"status": "submitted",
"submitted_at": "2026-04-21T15:07:52.791Z",
"lines": [
{ "line_number": 1, "style": "21104", "color": "BLK", "size": "M", "quantity": 2, "upc": "888555971156" }
]
}order.accepted · data
{
"order_id": "DS-001234",
"customer_po": "PO-12345",
"customer_code": "wick123",
"status": "accepted",
"erp_order_number": "1048392",
"accepted_at": "2026-04-21T15:08:22.000Z",
"line_results": [
{ "line_number": 1, "sql_id": "5820471", "success": true }
]
}order.shipped · data
{
"order_id": "DS-001234",
"customer_po": "PO-12345",
"customer_code": "wick123",
"shipment_id": "a2b8f501-7e4c-49aa-b3a7-d2f1e0cbd6a9",
"tracking_number": "1Z999AA10123456784",
"carrier": "UPS",
"ship_date": "2026-04-23T16:00:00.000Z",
"line_items": [
{ "line_number": 1, "upc": "888555971156", "quantity": 2 }
],
"order_complete": true,
"total_ordered": 2,
"total_shipped": 2
}When an order ships in multiple boxes, this event fires per shipment. order_complete is false on intermediate shipments and true on the final one.
order.error · data
{
"order_id": "DS-001234",
"customer_po": "PO-12345",
"customer_code": "wick123",
"status": "error",
"error_message": "Customer code 'wick123' not found in ERP",
"error_code": "CUST_NOT_FOUND",
"failed_at": "2026-04-21T15:08:22.000Z"
}Verifying the signature
The signature is an HMAC-SHA256 of the raw request body using your webhook secret, prefixed with sha256=. Always compare with a constant-time comparison to avoid timing attacks. Use the raw body as received — re-serializing the parsed JSON will produce a different hash.
import { createHmac, timingSafeEqual } from 'crypto';
export function verifyObeySignature(rawBody, headerValue, secret) {
const expected = 'sha256=' + createHmac('sha256', secret)
.update(rawBody)
.digest('hex');
const a = Buffer.from(headerValue || '');
const b = Buffer.from(expected);
return a.length === b.length && timingSafeEqual(a, b);
}
// Express example — note express.raw() to keep the body unparsed
app.post('/obey-webhook', express.raw({ type: 'application/json' }), (req, res) => {
const sig = req.headers['x-obey-signature'];
if (!verifyObeySignature(req.body, sig, process.env.OBEY_WEBHOOK_SECRET)) {
return res.status(401).end();
}
const event = JSON.parse(req.body.toString('utf8'));
// ... handle event ...
res.json({ ok: true });
});import hmac, hashlib
def verify_obey_signature(raw_body: bytes, header_value: str, secret: str) -> bool:
expected = "sha256=" + hmac.new(
secret.encode("utf-8"),
raw_body,
hashlib.sha256,
).hexdigest()
return hmac.compare_digest(expected, header_value or "")
# Flask example
@app.route("/obey-webhook", methods=["POST"])
def obey_webhook():
sig = request.headers.get("X-Obey-Signature", "")
if not verify_obey_signature(request.get_data(), sig, os.environ["OBEY_WEBHOOK_SECRET"]):
abort(401)
event = request.get_json()
# ... handle event ...
return {"ok": True}Retries & expectations
- Respond with HTTP 2xx within 10 seconds to acknowledge.
- Failed deliveries retry up to 3 times with exponential backoff (0s, 2s, 8s).
- Repeated failures bump
failure_count, visible from Settings; pause or resume any subscription. - Webhooks deliver at-least-once. Dedupe on your end by
data.order_id+eventif you persist them. - Order matters loosely but is not guaranteed. If you need strict ordering, fetch
GET /api/orders/:idwhen you receive a notification rather than rebuilding state from the events.
CSV upload format
Required columns and template
CSV and XLSX files share the same column schema. Headers are case-insensitive and may appear in any order. Each row becomes one order line.
Columns
| Field | Type | Required | Description |
|---|---|---|---|
| style | string | Required | Product style number (e.g. 21104) |
| color | string | Required | Color code (e.g. BLK, NAV) |
| size | string | Required | Size code (e.g. S, M, L, XL) |
| qty | integer | Required | Order quantity |
| price | decimal | Required | Unit price (e.g. 49.99) |
| upc | string | Required | 12-digit barcode. Required at submit; CSV parser accepts blank rows but submission will reject them. |
Order field reference
Status flow, header fields, line item fields
Status flow
Orders may also enter partially_shipped when shipments are recorded one box at a time, and error on any failure. Terminal statuses are shipped, cancelled, and error.
Order header
| Field | Type | Required | Description |
|---|---|---|---|
| customer_po | string | Required | Your purchase-order number, unique per order. |
| ship_name | string | Optional | Recipient name on the shipping label. |
| address1, address2 | string | Optional | Street address lines. |
| city, state, zip, country | string | Optional | Destination address. |
| phone, email | string | Optional | Contact info for the delivery. |
| start_ship_date, comp_ship_date | date | Optional | Earliest and cancel-by ship dates. |
| special_instructions | string | Optional | Free-text delivery notes. |
Order line
| Field | Type | Required | Description |
|---|---|---|---|
| style | string | Required | Obermeyer style number. |
| color | string | Required | Color code. |
| size | string | Required | Size code. |
| quantity | integer | Required | Units ordered. |
| upc | string | Required | 12-digit barcode (required at submit). |