Getting Started
The Sadler Beef Distribution API provides programmatic access to USDA beef inventory, pricing, and fulfillment. Build integrations to automate ordering, track shipments, and manage your supply chain.
Base URLs
https://api.sadler.com/v1https://sandbox.sadler.com/v1Quick Example
curl https://api.sadler.com/v1/products \ -H "Authorization: Bearer sk_live_..."
Authentication
Authenticate requests using your API key in the Authorization header. Each API key has a defined scope that determines which operations it can perform.
API Key Scopes
| Scope | Description |
|---|---|
| read_only | Read products, prices, and orders |
| orders_write | Create and modify orders |
| webhooks_manage | Configure webhook endpoints |
curl https://api.sadler.com/v1/products \ -H "Authorization: Bearer sk_live_4eC39HqLyjWDarjtT1zdp7dc"
Authentication Error
{
"error": {
"type": "authentication_error",
"message": "API key invalid or expired"
}
}Note: API keys can be created, rotated, and revoked from the Sadler dashboard. Keep your API keys secure and never expose them in client-side code.
Versioning
The API uses header-based versioning. Specify the API version using the Sadler-Version header.
curl https://api.sadler.com/v1/products \ -H "Authorization: Bearer sk_live_..." \ -H "Sadler-Version: 2024-01-15"
Idempotency
Idempotency keys prevent duplicate requests. Include an Idempotency-Key header with a unique UUID to safely retry requests without creating duplicate resources.
Supported Endpoints
POST /ordersPOST /paymentsPOST /shipments
curl -X POST https://api.sadler.com/v1/orders \ -H "Authorization: Bearer sk_live_..." \ -H "Idempotency-Key: 550e8400-e29b-41d4-a716-446655440000" \ -H "Content-Type: application/json" \ -d '{"items": [{"product_id": "prod_123", "quantity": 10}]}'
Retention: Idempotency keys are retained for 24 hours. After this period, the same key can be reused.
Conflict Response
{
"error": {
"type": "conflict",
"code": "idempotency_conflict",
"message": "Request conflicts with existing idempotent request"
}
}Pagination
List endpoints return paginated results using cursor-based pagination. Use the cursor parameter to retrieve the next page of results.
Available Filters
GET /products
category, grade, origin, imps_code, updated_since, limit, cursor
GET /prices
sku, origin, updated_since
GET /orders
status, buyer_id, created_since, limit, cursor
curl "https://api.sadler.com/v1/products?cursor=eyJwYWdlIjoyfQ&limit=10" \ -H "Authorization: Bearer sk_live_..."
{
"data": [...],
"has_more": true,
"next_cursor": "eyJwYWdlIjozfQ"
}Data Model
Product Object
| Field | Type | Description |
|---|---|---|
| id | string | Unique product identifier |
| name | string | Product name |
| category | string | Product category |
| price | number | Price in cents |
| inventory | number | Available quantity |
| lot_code | string | Production lot identifier |
| plant_est_number | string | USDA plant establishment number |
| production_date | string | Production date (ISO 8601) |
| best_by | string | Best by date (ISO 8601) |
Order Object
| Field | Type | Description |
|---|---|---|
| id | string | Unique order identifier |
| status | string | Order status |
| items | array | Order line items |
| total | number | Total in cents |
Catch-weight Pricing
All beef items are sold per pound and finalized based on actual scale weight. Orders are created with estimated weights, and final pricing is adjusted when the product is weighed during fulfillment.
Order (Estimated)
{
"items": [{
"sku": "BF-RIBEYE-112A-CHOICE",
"unit": "lb",
"quantity": 150,
"price_per_unit": 7.85,
"price_basis": "per_lb",
"weight_basis": "estimated"
}]
}Webhook (Finalized)
{
"type": "order.finalized",
"data": {
"order_id": "ord_9f2a1c",
"adjustments": {
"actual_weight_lb": 158.6,
"delta_amount": 61.21,
"final_grand_total": 1257.21
}
}
}Endpoints
List Products
/v1/productscurl https://api.sadler.com/v1/products \ -H "Authorization: Bearer sk_live_..."
Create Order
/v1/orderscurl -X POST https://api.sadler.com/v1/orders \ -H "Authorization: Bearer sk_live_..." \ -H "Content-Type: application/json" \ -d '{"items": [{"product_id": "prod_123", "quantity": 10}]}'
Create Quote
/v1/quotesCreate a temporary quote valid for 15 minutes. Use the quote_id when creating an order to lock pricing.
curl -X POST https://api.sadler.com/v1/quotes \ -H "Authorization: Bearer sk_live_..." \ -H "Content-Type: application/json" \ -d '{"items": [{"sku":"BF-RIBEYE-112A-CHOICE","unit":"lb","quantity":150,"origin":"IA"}]}'
{
"id": "qte_abc123",
"expires_at": "2025-10-26T17:20:00Z",
"totals": {
"estimated_grand_total": 1196.00
}
}List Lots
/v1/lotsRetrieve lot and traceability information for products.
curl "https://api.sadler.com/v1/lots?sku=BF-RIBEYE-112A-CHOICE" \ -H "Authorization: Bearer sk_live_..."
Webhooks
Webhooks allow you to receive real-time notifications about events in your account. Configure webhook endpoints in your dashboard.
Event Types
| Event | Description |
|---|---|
| product.updated | Product information changed |
| price.updated | Pricing changed |
| order.created | New order placed |
| order.finalized | Order weight finalized |
| payment.captured | Payment processed |
| shipment.dispatched | Order shipped |
| shipment.delivered | Order delivered |
| order.expired | Reservation expired |
Signature Verification
Verify webhook signatures using HMAC SHA256. The signature is included in the Sadler-Signature header.
Header format: Sadler-Signature: t=timestamp, v1=signature
JavaScript
import crypto from "crypto"; function verifySig(rawBody, header, secret) { const parts = Object.fromEntries( header.split(",").map(kv => kv.trim().split("=")) ); const payload = `${parts.t}.${rawBody}`; const digest = crypto .createHmac("sha256", secret) .update(payload) .digest("hex"); const safeEq = crypto.timingSafeEqual( Buffer.from(digest), Buffer.from(parts.v1) ); const ageOk = Math.abs(Date.now()/1000 - Number(parts.t)) < 300; return safeEq && ageOk; }
Python
import hmac, hashlib, time def verify_sig(raw_body: bytes, header: str, secret: str) -> bool: parts = dict( p.split("=",1) for p in [s.strip() for s in header.split(",")] ) payload = f"{parts['t']}.{raw_body.decode()}".encode() digest = hmac.new( secret.encode(), payload, hashlib.sha256 ).hexdigest() fresh = abs(time.time() - int(parts["t"])) < 300 return hmac.compare_digest(digest, parts["v1"]) and fresh
Retries: Failed webhooks are retried with exponential backoff for up to 24 hours. Return a 2xx status code to acknowledge receipt.
Errors
The API uses standard HTTP status codes and returns error details in JSON format with type, code, and message fields.
| HTTP | Type | Code | Message |
|---|---|---|---|
| 400 | validation_error | invalid_quantity | quantity must be greater than zero |
| 401 | authentication_error | invalid_api_key | API key is missing or invalid |
| 403 | permission_error | scope_denied | key lacks required scope |
| 404 | not_found | sku_not_found | product not found |
| 409 | conflict | idempotency_conflict | conflicting idempotent request |
| 429 | rate_limited | too_many_requests | rate limit exceeded |
| 500 | internal_error | internal | unexpected error |
Rate Limits
The API enforces a default rate limit of 600 requests per minute per API key, with burst capacity up to 1200 requests per minute for 60 seconds.
Response Headers
X-RateLimit-Limit: 600 X-RateLimit-Remaining: 421 X-RateLimit-Reset: 1730001234
429 Response: When rate limited, respect the Retry-After header and use exponential backoff with jitter for retries.
Sandbox
Test your integration using the sandbox environment. No live funds move in sandbox mode, and test data is preloaded for development.
Sandbox URL: https://sandbox.api.sadler.com
Trigger Test Events
curl -X POST https://sandbox.api.sadler.com/sandbox/events \ -H "Authorization: Bearer sk_test_..." \ -H "Content-Type: application/json" \ -d '{"type": "payment.captured", "order_id": "ord_test_1"}'
Changelog
2024-01-15
Latest- • Added webhook signature verification
- • Improved error messages
- • Added pagination support
2023-12-01
- • Initial API release
- • Products and Orders endpoints
