Getting Started
Emporia Post API
The Emporia Post REST API gives you programmatic access to multi-carrier shipping rates, label purchasing, address validation, and tracking — all through a single, consistent interface backed by the eHub carrier network.
Base URL
/api/public/v1
Auth
Bearer Token
Format
JSON
Base URL
https://your-domain.manus.space/api/public/v1Replace your-domain with your Emporia Post deployment domain.
Authentication
All API requests (except GET /ping) require a valid API key passed as a Bearer token in the Authorization header.
Authorization: Bearer ep_live_YOUR_API_KEY
Scopes
Each API key is issued with one or more scopes that control which endpoints it can access.
| Scope | Endpoints | Description |
|---|---|---|
| rates | /rates, /addresses/validate | Fetch carrier rates and validate addresses |
| labels | /labels (all methods) | Purchase, retrieve, and void labels |
| tracking | /tracking/:number | Look up tracking status and events |
Rate Limits
Rate limits are enforced per API key using a sliding 60-second window. The default limit is 60 requests per minute. You can configure a higher limit (up to 300 req/min) when creating a key in the Developer Portal.
| HTTP Status | Condition | Response |
|---|---|---|
| 429 | Rate limit exceeded | { error: "rate_limit_exceeded", retryAfter: 60 } |
When you receive a 429, wait retryAfter seconds before retrying. Implement exponential backoff for production systems.
Error Codes
All errors return a JSON body with error (machine-readable code) and message (human-readable description).
{
"error": "unauthorized",
"message": "Missing or malformed Authorization header. Expected: Bearer <api_key>"
}| Status | Error Code | Meaning |
|---|---|---|
| 200 | — | Success |
| 201 | — | Resource created (label purchased) |
| 400 | bad_request | Missing or invalid request parameters |
| 401 | unauthorized | Missing, invalid, or revoked API key |
| 403 | forbidden | API key lacks the required scope |
| 404 | not_found | Resource not found |
| 429 | rate_limit_exceeded | Too many requests — slow down |
| 500 | internal_error | Unexpected server error |
| 503 | service_unavailable | eHub API key not configured or service down |
API Reference
Endpoints
/api/public/v1/pingReturns the current API status and version. No authentication required. Use this to verify connectivity before making authenticated requests.
Response
| Field | Type | Description |
|---|---|---|
| status | string | Always "ok" when the service is healthy |
| service | string | Service name: "Emporia Post API" |
| version | string | Current API version string |
| timestamp | string | ISO 8601 UTC timestamp of the response |
Example
curl https://your-domain.manus.space/api/public/v1/ping
/api/public/v1/addresses/validatescope: ratesValidates a shipping address against the carrier network. Returns a corrected address and any validation messages. Call this before fetching rates or purchasing labels to reduce failed deliveries.
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
| address | object | required | Address object to validate (see Address Object below) |
| address.address1 | string | required | Street address line 1 |
| address.city | string | required | City name |
| address.state | string | required | 2-letter state code (US) |
| address.country | string | required | 2-letter ISO country code (e.g. "US") |
| address.postal_code | string | required | ZIP or postal code |
Response
| Field | Type | Description |
|---|---|---|
| data.valid | boolean | Whether the address is deliverable |
| data.address | object | Corrected/normalized address object |
| data.messages | string[] | Validation warnings or correction notes |
Example
curl -X POST https://your-domain.manus.space/api/public/v1/addresses/validate \
-H "Authorization: Bearer ep_live_YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{
"address": {
"address1": "123 Main St",
"city": "Austin",
"state": "TX",
"country": "US",
"postal_code": "78701"
}
}'/api/public/v1/ratesscope: ratesReturns available carrier rates for a shipment. Results are sorted by price ascending. Filter by carrier or service level on the client side using the carrier_code and service_code fields.
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
| fromAddress | object | required | Sender address object |
| toAddress | object | required | Recipient address object |
| parcel | object | required | Package dimensions and weight |
| parcel.length | number | required | Length in inches |
| parcel.width | number | required | Width in inches |
| parcel.height | number | required | Height in inches |
| parcel.weight | number | required | Weight in pounds |
Response
| Field | Type | Description |
|---|---|---|
| data | Rate[] | Array of available service rates, sorted by price |
| data[].service_id | number | Use this ID to purchase a label |
| data[].carrier | string | Carrier display name (e.g. "USPS") |
| data[].carrier_code | string | Machine-readable carrier code |
| data[].service | string | Service level name |
| data[].rate | number | Cost in USD |
| data[].delivery_days | number | Estimated transit days |
| count | number | Total number of rates returned |
Example
curl -X POST https://your-domain.manus.space/api/public/v1/rates \
-H "Authorization: Bearer ep_live_YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{
"fromAddress": {
"address1": "123 Main St", "city": "Austin",
"state": "TX", "country": "US", "postal_code": "78701"
},
"toAddress": {
"address1": "456 Oak Ave", "city": "Denver",
"state": "CO", "country": "US", "postal_code": "80203"
},
"parcel": { "length": 10, "width": 8, "height": 4, "weight": 1.5 }
}'/api/public/v1/labelsscope: labelsPurchases a shipping label for the selected service. Returns a label URL, tracking number, and cost. The label URL is valid for 24 hours — download and store it immediately.
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
| fromAddress | object | required | Sender address object |
| toAddress | object | required | Recipient address object |
| parcel | object | required | Package dimensions and weight |
| serviceId | number | required | service_id from a /rates response |
| labelFormat | string | optional | "pdf" (default), "png", or "zpl" |
Response
| Field | Type | Description |
|---|---|---|
| data.labelId | string | Unique label identifier (use for GET/DELETE) |
| data.trackingNumber | string | Carrier tracking number |
| data.carrier | string | Carrier code |
| data.service | string | Service level name |
| data.cost | number | Amount charged in USD |
| data.labelUrl | string | Temporary URL to download the label file |
| data.status | string | Initial status: "purchased" |
Example
curl -X POST https://your-domain.manus.space/api/public/v1/labels \
-H "Authorization: Bearer ep_live_YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{
"fromAddress": { "address1": "123 Main St", "city": "Austin", "state": "TX", "country": "US", "postal_code": "78701" },
"toAddress": { "address1": "456 Oak Ave", "city": "Denver", "state": "CO", "country": "US", "postal_code": "80203" },
"parcel": { "length": 10, "width": 8, "height": 4, "weight": 1.5 },
"serviceId": 12345,
"labelFormat": "pdf"
}'/api/public/v1/labels/:labelIdscope: labelsRetrieves the current status and metadata for a previously purchased label. Use the labelId returned from the POST /labels endpoint.
Response
| Field | Type | Description |
|---|---|---|
| data.labelId | string | Label identifier |
| data.trackingNumber | string | Carrier tracking number |
| data.carrier | string | Carrier code |
| data.status | string | Current status: purchased | in_transit | delivered | cancelled |
| data.cost | number | Amount paid in USD |
| data.labelUrl | string | Label download URL |
| data.createdAt | string | ISO 8601 creation timestamp |
Example
curl https://your-domain.manus.space/api/public/v1/labels/LABEL_ID \ -H "Authorization: Bearer ep_live_YOUR_KEY"
/api/public/v1/labels/:labelIdscope: labelsVoids (cancels) a purchased label. Most carriers allow voiding within 30 days of purchase. Voided labels cannot be used for shipping. Refunds are subject to carrier policy.
Response
| Field | Type | Description |
|---|---|---|
| data.voided | boolean | true if the label was successfully voided |
| data.labelId | string | The label ID that was voided |
Example
curl -X DELETE https://your-domain.manus.space/api/public/v1/labels/LABEL_ID \ -H "Authorization: Bearer ep_live_YOUR_KEY"
/api/public/v1/tracking/:trackingNumberscope: trackingReturns the current tracking status and event history for a shipment by tracking number. Works with any carrier supported by eHub.
Response
| Field | Type | Description |
|---|---|---|
| data.status | string | Current tracking status |
| data.trackingNumber | string | The tracking number queried |
| data.carrier | string | Carrier code |
| data.events | Event[] | Array of tracking events, newest first |
| data.events[].status | string | Event status label |
| data.events[].description | string | Human-readable event description |
| data.events[].location | string | Location of the event |
| data.events[].event_time | string | ISO 8601 event timestamp |
Example
curl https://your-domain.manus.space/api/public/v1/tracking/9400111899223397846059 \ -H "Authorization: Bearer ep_live_YOUR_KEY"
Platform Guides
eBay Integration
Emporia Post connects to your eBay seller account via OAuth 2.0. Once authorized, orders are automatically imported and tracking numbers are pushed back to eBay to mark items as shipped — no manual copy-paste required.
Prerequisites
- An active eBay seller account
- An eBay Developer account at developer.ebay.com
- An eBay application with sell.fulfillment and sell.fulfillment.readonly scopes
Setup Steps
- Go to the eBay Developer Portal → Application Keys and create a new application.
- Under User Tokens, create an OAuth redirect URL (RuName) pointing to:
https://your-domain.manus.space/api/ebay/callback - In Emporia Post, go to Settings → Secrets and add:
EBAY_CLIENT_ID,EBAY_CLIENT_SECRET,EBAY_RU_NAME - Navigate to Integrations and click Authorize with eBay. You will be redirected to eBay's consent screen.
- After authorizing, you will be redirected back to Emporia Post and the connection will be active.
How Sync Works
| Direction | Trigger | What happens |
|---|---|---|
| eBay → Emporia Post | Manual "Sync Orders" button | Fetches open orders from eBay Fulfillment API and imports them as platform orders |
| Emporia Post → eBay | Label purchased | Calls createShippingFulfillment with tracking number and carrier to mark item as shipped |
Shopify Integration
Connect your Shopify store using a private app API key. Emporia Post imports unfulfilled orders and pushes fulfillment data (tracking number and carrier) back to Shopify when a label is purchased.
Setup Steps
- In your Shopify admin, go to Apps → Develop apps and create a new app.
- Under Configuration, grant the following Admin API scopes:
read_orders,write_fulfillments - Install the app and copy the Admin API access token.
- In Emporia Post, go to Integrations → Shopify → Connect and enter your store domain (e.g.
my-store.myshopify.com) and API token.
| Direction | API Used |
|---|---|
| Order import | GET /admin/api/2024-01/orders.json?fulfillment_status=unfulfilled |
| Tracking push | POST /admin/api/2024-01/orders/{id}/fulfillments.json |
Etsy Integration
Connect your Etsy shop using the Etsy Open API v3. Emporia Post imports open receipts (orders) and pushes tracking numbers back to Etsy when a label is purchased.
Setup Steps
- Create an app at etsy.com/developers/register.
- Request the transactions_r and transactions_w scopes.
- Generate an API key and your Shop ID (visible in your Etsy shop URL).
- In Emporia Post, go to Integrations → Etsy → Connect and enter your API key and Shop ID.
| Direction | API Used |
|---|---|
| Order import | GET /v3/application/shops/{shopId}/receipts?was_shipped=false |
| Tracking push | POST /v3/application/shops/{shopId}/receipts/{receiptId}/tracking |
Poshmark Integration
CSV Import Workflow
- In your Poshmark account, go to My Sales and export your orders as a CSV.
- In Emporia Post, go to Integrations → Poshmark → Import CSV.
- Upload the CSV file. Emporia Post will map the columns and import the orders.
- After purchasing a label, copy the tracking number and manually update the Poshmark order to mark it as shipped.
CSV Column Mapping
| Poshmark Column | Maps To |
|---|---|
| Order ID | platformOrderId |
| Buyer Username | buyerName |
| Buyer Address | toAddress.address1 |
| Item Title | orderDetails |
| Sale Price | orderValue |
ThredUp Integration
Follow the same CSV import workflow as Poshmark. Export your ThredUp order data, import it into Emporia Post, purchase labels, and manually update ThredUp with the tracking numbers.
Generic Webhook Integration
The Generic Webhook connection lets any platform push orders to Emporia Post via HTTP POST, and receive tracking information back via an outbound webhook when a label is purchased.
Inbound Webhook (Orders → Emporia Post)
Send a POST request to your webhook endpoint with the order payload:
POST https://your-domain.manus.space/api/webhook/orders
X-Webhook-Secret: YOUR_WEBHOOK_SECRET
Content-Type: application/json
{
"platformOrderId": "ORDER-12345",
"buyerName": "Jane Smith",
"buyerEmail": "[email protected]",
"toAddress": {
"address1": "456 Oak Ave",
"city": "Denver",
"state": "CO",
"country": "US",
"postal_code": "80203"
},
"orderValue": 49.99,
"orderDetails": "Blue Vintage Jacket, Size M"
}Outbound Webhook (Tracking → Your Platform)
When a label is purchased for a webhook order, Emporia Post sends a POST to your configured callback URL:
POST https://your-platform.com/emporia-callback
Content-Type: application/json
{
"event": "label.purchased",
"platformOrderId": "ORDER-12345",
"trackingNumber": "9400111899223397846059",
"carrier": "USPS",
"service": "Priority Mail",
"cost": 8.45,
"labelUrl": "https://..."
}Security
All inbound requests must include the X-Webhook-Secret header matching the secret shown in your Integrations settings. Requests without a valid secret are rejected with HTTP 401.