REST API Documentation
Integrate CostDock into your applications. Create shipping labels, validate addresses, track packages, and manage batches programmatically.
Authentication
All API requests require a valid API key passed as a Bearer token in the Authorization header. Generate a key from your account settings.
curl -H "Authorization: Bearer sk_live_your_api_key_here" \
https://app.costdock.com/api/v1/creditsSecurity: Keep your API key private. Never expose it in client-side code or commit it to version control.
Response Format
All responses follow a consistent JSON structure:
Success
{
"success": true,
"data": { ... }
}Error
{
"success": false,
"error": {
"code": "INSUFFICIENT_CREDITS",
"message": "Not enough credits"
}
}Rate Limiting
API requests are limited to 100 requests per minute per API key. Rate limit info is included in response headers:
| Header | Description |
|---|---|
| X-RateLimit-Limit | Requests allowed per window |
| X-RateLimit-Remaining | Requests remaining |
| X-RateLimit-Reset | Unix timestamp when window resets |
| Retry-After | Seconds to wait (only on 429 responses) |
Idempotency
All POST requests (rates, shipments, batches) require an Idempotency-Key header. If you retry a request with the same key within 24 hours, you'll get the original response back without creating a duplicate.
curl -X POST \
-H "Authorization: Bearer sk_live_..." \
-H "Idempotency-Key: unique-request-id-123" \
-H "Content-Type: application/json" \
-d '{"from": {...}, "to": {...}, "parcel": {...}}' \
https://app.costdock.com/api/v1/shipmentsEndpoints
/api/v1/creditsGet your current credit balance.
Request
curl -H "Authorization: Bearer sk_live_..." \
https://app.costdock.com/api/v1/creditsResponse
{
"success": true,
"data": {
"balance_cents": 5000,
"formatted_balance": "$50.00"
}
}/api/v1/ratesGet shipping rates for a package. Returns available carriers, services, and prices.
Request
curl -X POST \
-H "Authorization: Bearer sk_live_..." \
-H "Idempotency-Key: rates-unique-id-123" \
-H "Content-Type: application/json" \
-d '{
"from": {
"name": "John Doe",
"street1": "123 Main St",
"city": "San Francisco",
"state": "CA",
"zip": "94105",
"country": "US"
},
"to": {
"name": "Jane Smith",
"street1": "456 Oak Ave",
"city": "Los Angeles",
"state": "CA",
"zip": "90001",
"country": "US"
},
"parcel": {
"packageType": "package",
"lengthIn": 10,
"widthIn": 8,
"heightIn": 4,
"weightLbs": 1,
"deliveryOption": "none"
}
}' \
https://app.costdock.com/api/v1/ratesResponse
{
"success": true,
"data": {
"rates": [
{
"carrier": "USPS",
"service": "Priority",
"rate_cents": 825,
"delivery_days": 3
}
]
}
}Dimensions required for packages: lengthIn, widthIn, and heightIn are required when packageType is "package". UPS and FedEx cannot return rates without dimensions. Omitting them will limit results to USPS only. Weight can be specified as weightLbs (recommended) or weightOz (if both provided, weightOz takes precedence). Optional fields: deliveryOption ("none" | "signature" | "adult_signature"), isReturn (boolean), skip_address_verification (boolean, default false — skip address verification for addresses that fail validation but are known to be correct).
/api/v1/addresses/validateValidate and normalize an address. Returns corrected address with residential/commercial indicator.
Request
curl -X POST \
-H "Authorization: Bearer sk_live_..." \
-H "Content-Type: application/json" \
-d '{
"address": {
"name": "John Doe",
"street1": "123 Main St",
"city": "San Francisco",
"state": "CA",
"zip": "94105",
"country": "US"
}
}' \
https://app.costdock.com/api/v1/addresses/validateResponse
{
"success": true,
"data": {
"valid": true,
"address": {
"street1": "123 MAIN ST",
"street2": null,
"city": "SAN FRANCISCO",
"state": "CA",
"zip": "94105",
"country": "US",
"name": "JOHN DOE",
"company": null,
"phone": null,
"residential": false
}
}
}/api/v1/shipments?page=1&per_page=20List your shipments with pagination.
Request
curl -H "Authorization: Bearer sk_live_..." \
"https://app.costdock.com/api/v1/shipments?page=1&per_page=20"Response
{
"success": true,
"data": {
"shipments": [...],
"total_count": 42,
"page": 1,
"per_page": 20
}
}/api/v1/shipmentsCreate and purchase a shipping label. Returns 201 Created. Optionally specify carrier and service from a rates response — otherwise the cheapest rate is selected automatically. Deducts credits.
Request
curl -X POST \
-H "Authorization: Bearer sk_live_..." \
-H "Idempotency-Key: ship-456" \
-H "Content-Type: application/json" \
-d '{
"from": {
"name": "John Doe",
"company": "Acme Corp",
"street1": "123 Main St",
"street2": "Suite 400",
"city": "San Francisco",
"state": "CA",
"zip": "94105",
"country": "US",
"phone": "415-555-0100"
},
"to": {
"name": "Jane Smith",
"street1": "456 Oak Ave",
"city": "Los Angeles",
"state": "CA",
"zip": "90001",
"country": "US"
},
"parcel": {
"packageType": "package",
"lengthIn": 10,
"widthIn": 8,
"heightIn": 4,
"weightLbs": 1,
"deliveryOption": "signature",
"labelFormat": "PDF"
},
"carrier": "USPS",
"service": "Priority"
}' \
https://app.costdock.com/api/v1/shipmentsResponse
{
"success": true,
"data": {
"id": "uuid",
"tracking_code": "1Z999AA10123456784",
"label_url": "https://...",
"label_pdf_url": "https://...",
"label_zpl_url": "https://...",
"carrier": "USPS",
"service": "Priority",
"rate_cents": 877,
"status": "purchased",
"public_tracking_url": "https://...",
"created_at": "2026-02-20T00:00:00Z"
}
}Optional fields: Address objects also accept company, street2, and phone. Set packageType to envelope for letters and flat mailers (no dimensions needed, cheaper rates). Parcel also accepts deliveryOption (signature, adult_signature) and labelFormat (PNG, PDF, ZPL). Set skip_address_verification to true to bypass address verification for addresses that fail validation but are known to be correct. See Address Fields and Parcel Fields for the full schema.
Two-step flow: Call POST /api/v1/rates first to show available carriers and prices, then pass the customer's chosen carrier and service to POST /api/v1/shipments. If omitted, the cheapest available rate is selected automatically.
/api/v1/shipments/:idGet details for a specific shipment.
Request
curl -H "Authorization: Bearer sk_live_..." \
https://app.costdock.com/api/v1/shipments/uuid-hereResponse
{
"success": true,
"data": {
"id": "uuid",
"tracking_code": "1Z999AA10123456784",
"label_url": "https://...",
"label_pdf_url": "https://...",
"label_zpl_url": "https://...",
"carrier": "USPS",
"service": "Priority",
"rate_cents": 877,
"status": "purchased",
"public_tracking_url": "https://...",
"created_at": "2026-02-20T00:00:00Z"
}
}/api/v1/shipments/:id/voidVoid a purchased label and refund credits. Subject to carrier-specific void windows (USPS: 30 days, UPS/FedEx: 90 days).
Request
curl -X POST \
-H "Authorization: Bearer sk_live_..." \
https://app.costdock.com/api/v1/shipments/uuid-here/voidResponse
{
"success": true,
"data": {
"id": "uuid",
"status": "voided",
"refunded_amount_cents": 825
}
}/api/v1/tracking/:codeGet tracking status and events for a shipment.
Request
curl -H "Authorization: Bearer sk_live_..." \
https://app.costdock.com/api/v1/tracking/1Z999AA10123456784Response
{
"success": true,
"data": {
"tracking_code": "1Z999AA10123456784",
"status": "in_transit",
"events": [
{
"status": "in_transit",
"message": "Package in transit",
"datetime": "2026-02-12T10:00:00Z",
"location": "San Francisco, CA"
}
]
}
}/api/v1/batches?page=1&per_page=20List your batches with pagination.
Request
curl -H "Authorization: Bearer sk_live_..." \
"https://app.costdock.com/api/v1/batches?page=1&per_page=20"Response
{
"success": true,
"data": {
"batches": [...],
"total_count": 5,
"page": 1,
"per_page": 20
}
}/api/v1/batchesCreate a batch of shipments for bulk label purchase. Returns 201 Created. Accepts 1-1000 shipments.
Request
curl -X POST \
-H "Authorization: Bearer sk_live_..." \
-H "Idempotency-Key: batch-789" \
-H "Content-Type: application/json" \
-d '{
"shipments": [
{
"from": {
"name": "John Doe",
"company": "Acme Corp",
"street1": "123 Main St",
"street2": "Suite 400",
"city": "San Francisco",
"state": "CA",
"zip": "94105",
"country": "US",
"phone": "415-555-0100"
},
"to": {
"name": "Jane Smith",
"street1": "456 Oak Ave",
"city": "Los Angeles",
"state": "CA",
"zip": "90001",
"country": "US"
},
"parcel": {
"packageType": "package",
"lengthIn": 10,
"widthIn": 8,
"heightIn": 4,
"weightLbs": 1,
"deliveryOption": "signature",
"labelFormat": "PDF"
}
},
{
"from": {...},
"to": {...},
"parcel": {...}
}
]
}' \
https://app.costdock.com/api/v1/batchesResponse
{
"success": true,
"data": {
"id": "uuid",
"status": "draft",
"total_shipments": 2,
"created_at": "2026-02-20T00:00:00Z"
}
}Optional fields: Each shipment's address accepts company, street2, and phone. Set packageType to envelope for letters and flat mailers (no dimensions needed, cheaper rates). Parcel also accepts deliveryOption (signature, adult_signature) and labelFormat (PNG, PDF, ZPL). See Address Fields and Parcel Fields for the full schema.
/api/v1/batches/:idGet details for a specific batch including its shipments.
Request
curl -H "Authorization: Bearer sk_live_..." \
https://app.costdock.com/api/v1/batches/uuid-hereResponse
{
"success": true,
"data": {
"id": "uuid",
"status": "completed",
"total_shipments": 10,
"purchased_count": 9,
"failed_count": 1,
"total_cost_cents": 11250,
"created_at": "2026-02-20T00:00:00Z",
"updated_at": "2026-02-20T00:15:00Z",
"shipments": [
{
"id": "uuid",
"tracking_code": "1Z999AA10123456784",
"carrier": "UPS",
"service": "Ground",
"customer_rate_cents": 1250,
"status": "purchased",
"created_at": "2026-02-20T00:15:00Z"
}
]
}
}/api/v1/batches/:id/purchasePurchase all labels in a draft batch. Verifies addresses, fetches rates, holds credits, and creates labels. Batch must be in 'draft' status.
Request
curl -X POST \
-H "Authorization: Bearer sk_live_..." \
-H "Idempotency-Key: purchase-batch-789" \
https://app.costdock.com/api/v1/batches/uuid-here/purchaseResponse
{
"success": true,
"data": {
"id": "uuid",
"status": "purchasing",
"total_shipments": 10,
"purchased_count": 9,
"failed_count": 1,
"total_cost_cents": 11250,
"errors": [
{ "index": 3, "to_name": "Jane Smith", "to_city": "Los Angeles", "error": "From address: Invalid ZIP code" }
]
}
}Webhooks
Configure webhook endpoints to receive real-time event notifications. Manage endpoints from your account settings or via the API below.
Available Events
Webhook Payloads
Payloads vary by event type. All include type and data fields.
shipment.created / shipment.purchased
{
"type": "shipment.created",
"data": {
"id": "uuid",
"tracking_code": "1Z999AA10123456784",
"label_url": "https://...",
"carrier": "UPS",
"service": "Ground",
"rate_cents": 877,
"status": "purchased",
"public_tracking_url": "https://...",
"created_at": "2026-02-20T00:00:00Z"
}
}shipment.in_transit / shipment.delivered / shipment.returned / shipment.failed
{
"type": "shipment.in_transit",
"data": {
"id": "uuid",
"tracking_code": "1Z999AA10123456784",
"status": "in_transit",
"carrier": "UPS",
"service": "Ground",
"public_tracking_url": "https://...",
"tracking_events": [
{
"datetime": "2026-02-21T14:30:00Z",
"message": "Package in transit to destination",
"status": "in_transit",
"tracking_location": {
"city": "Memphis",
"state": "TN",
"country": "US"
}
}
]
}
}shipment.voided
{
"type": "shipment.voided",
"data": {
"id": "uuid",
"status": "voided",
"refunded_amount_cents": 877
}
}Webhook API Endpoints
/api/v1/webhooks?page=1&per_page=20List your webhook endpoints with pagination.
Request
curl -H "Authorization: Bearer sk_live_..." \
"https://app.costdock.com/api/v1/webhooks?page=1&per_page=20"Response
{
"success": true,
"data": {
"endpoints": [
{
"id": "uuid",
"url": "https://example.com/webhooks",
"enabled": true,
"events": ["shipment.created", "shipment.delivered"],
"created_at": "2026-02-20T00:00:00Z"
}
],
"total_count": 1,
"page": 1,
"per_page": 20
}
}/api/v1/webhooksCreate a new webhook endpoint. URL must use HTTPS and cannot point to private/internal addresses. Returns the signing key in the response — save it for signature verification.
Request
curl -X POST \
-H "Authorization: Bearer sk_live_..." \
-H "Content-Type: application/json" \
-d '{
"url": "https://example.com/webhooks/costdock",
"events": ["shipment.created", "shipment.delivered"]
}' \
https://app.costdock.com/api/v1/webhooksResponse
{
"success": true,
"data": {
"id": "uuid",
"url": "https://example.com/webhooks/costdock",
"enabled": true,
"events": ["shipment.created", "shipment.delivered"],
"signing_key": "whsec_...",
"created_at": "2026-02-20T00:00:00Z"
}
}/api/v1/webhooks/:idGet a specific webhook endpoint including its signing key.
Request
curl -H "Authorization: Bearer sk_live_..." \
https://app.costdock.com/api/v1/webhooks/uuid-hereResponse
{
"success": true,
"data": {
"id": "uuid",
"url": "https://example.com/webhooks/costdock",
"enabled": true,
"events": ["shipment.created"],
"signing_key": "whsec_...",
"created_at": "2026-02-20T00:00:00Z",
"updated_at": "2026-02-20T00:00:00Z"
}
}/api/v1/webhooks/:idUpdate a webhook endpoint. All fields are optional.
Request
curl -X PATCH \
-H "Authorization: Bearer sk_live_..." \
-H "Content-Type: application/json" \
-d '{"enabled": false}' \
https://app.costdock.com/api/v1/webhooks/uuid-hereResponse
{
"success": true,
"data": {
"id": "uuid",
"url": "https://example.com/webhooks/costdock",
"enabled": false,
"events": ["shipment.created"],
"created_at": "2026-02-20T00:00:00Z",
"updated_at": "2026-02-20T01:00:00Z"
}
}/api/v1/webhooks/:idDelete a webhook endpoint. This stops all event delivery to this URL.
Request
curl -X DELETE \
-H "Authorization: Bearer sk_live_..." \
https://app.costdock.com/api/v1/webhooks/uuid-hereResponse
{
"success": true,
"data": {
"deleted": true
}
}Signature Verification
Every webhook request includes signature headers that you should verify to ensure the payload was sent by CostDock and not tampered with. Use the svix SDK for easy verification.
Node.js
import { Webhook } from "svix";
const wh = new Webhook(process.env.COSTDOCK_WEBHOOK_SIGNING_KEY);
app.post("/webhooks/costdock", (req, res) => {
try {
const payload = wh.verify(req.body, {
"svix-id": req.headers["svix-id"],
"svix-timestamp": req.headers["svix-timestamp"],
"svix-signature": req.headers["svix-signature"],
});
// payload is verified — process it
console.log("Event type:", payload.type);
res.status(200).json({ received: true });
} catch (err) {
console.error("Webhook verification failed:", err);
res.status(400).json({ error: "Invalid signature" });
}
});Python
from svix.webhooks import Webhook
import os
wh = Webhook(os.environ["COSTDOCK_WEBHOOK_SIGNING_KEY"])
@app.route("/webhooks/costdock", methods=["POST"])
def handle_webhook():
try:
payload = wh.verify(request.get_data(), {
"svix-id": request.headers.get("svix-id"),
"svix-timestamp": request.headers.get("svix-timestamp"),
"svix-signature": request.headers.get("svix-signature"),
})
# payload is verified — process it
print("Event type:", payload["type"])
return {"received": True}, 200
except Exception as e:
print(f"Webhook verification failed: {e}")
return {"error": "Invalid signature"}, 400Important: Always verify webhook signatures before processing events. Responding with 2xx without verification opens your endpoint to spoofed events.
Retry Policy
Webhooks are delivered via Svix with automatic retries. If your endpoint returns a non-2xx status code or times out, the delivery will be retried with exponential backoff.
| Attempt | Delay |
|---|---|
| 1st retry | ~5 seconds |
| 2nd retry | ~5 minutes |
| 3rd retry | ~30 minutes |
| 4th retry | ~2 hours |
| 5th retry | ~8 hours |
Reliability: Failed deliveries are retried approximately 6 times over ~8 hours. After all retries are exhausted, the endpoint is automatically disabled.
Data Reference
Reference values you'll encounter in API responses and webhook payloads. Use these to build switch statements, filters, and routing logic in your integration.
Address Fields
The from and to objects share this schema. Either name or company is required.
| Field | Required | Format | Notes |
|---|---|---|---|
| name | Yes* | String (max 100) | Recipient or sender full name |
| company | No* | String (max 100) | Company name — can substitute for name |
| street1 | Yes | String (max 200) | Primary street address |
| street2 | No | String (max 200) | Apt, suite, unit, floor |
| city | Yes | String (max 100) | City |
| state | Yes | 2-letter code | US state abbreviation (e.g. CA, NY) |
| zip | Yes | 5-10 chars | ZIP or ZIP+4 (e.g. 94102, 94102-1234) |
| country | Yes | 2-letter code | Country code (US) |
| phone | No | String (max 20) | Phone number |
* At least one of name or company must be provided.
Parcel Fields
The parcel object describes the package being shipped. Used in rates, shipments, and batch endpoints.
| Field | Required | Valid Values | Default | Notes |
|---|---|---|---|---|
| packageType | No | packageenvelope | package | envelope uses standard flat dims (11x6x0.25") |
| weightLbs | Yes* | Number (max 150) | — | Weight in pounds (recommended) |
| weightOz | Yes* | Number (max 2400) | — | Weight in ounces — backwards compatible, takes precedence if both provided |
| lengthIn | If package | Number (max 108) | — | Length in inches |
| widthIn | If package | Number (max 108) | — | Width in inches |
| heightIn | If package | Number (max 108) | — | Height in inches |
| deliveryOption | No | nonesignatureadult_signature | none | Signature confirmation level |
| labelFormat | No | PNGPDFZPL | PNG | ZPL for thermal printers |
* Provide weightLbs (recommended) or weightOz (at least one required). Dimensions are required for package type — UPS and FedEx won't return rates without them.
Carriers & Services
The carrier and service fields appear in shipment responses, rate quotes, and webhook payloads. Values come from EasyPost and depend on which carrier accounts are connected.
| Carrier | Common Services | Void Window |
|---|---|---|
| USPS | PriorityExpressFirstGroundAdvantageParcelSelect | 30 days |
| UPS | Ground3DaySelect2ndDayAirNextDayAirSaverNextDayAir | 90 days |
| FedEx | GROUND_HOME_DELIVERYFEDEX_EXPRESS_SAVERFEDEX_2_DAYPRIORITY_OVERNIGHTSTANDARD_OVERNIGHT | 90 days |
Note: Exact service names depend on carrier configuration. Use the POST /api/v1/rates endpoint to discover available services for a specific origin/destination pair.
Shipment Statuses
The status field on shipments progresses through these values:
| Status | Description | Terminal? |
|---|---|---|
| quoted | Rate quote generated, label not yet purchased | No |
| purchased | Label purchased, awaiting carrier pickup | No |
| in_transit | Package picked up and moving through carrier network | No |
| delivered | Package delivered to recipient | Yes |
| voided | Label voided and credits refunded | Yes |
| returned | Package returned to sender | Yes |
| rejected | Label rejected by admin | Yes |
| failed | Label purchase or delivery failed | Yes |
Error Codes
| Code | Status | Description |
|---|---|---|
| UNAUTHORIZED | 401 | Invalid or missing API key |
| INVALID_REQUEST | 400/422 | Request validation failed (422 for address validation errors) |
| INSUFFICIENT_CREDITS | 402 | Not enough credits for this operation |
| AUTOPAY_FAILED | 403 | Autopay payment failed, add credits manually |
| NOT_FOUND | 404 | Resource not found |
| CONFLICT | 409 | Duplicate request or idempotency conflict |
| PAYLOAD_TOO_LARGE | 413 | Request body exceeds size limit |
| LIMIT_EXCEEDED | 422 | Resource limit reached (e.g. max webhooks) |
| RATE_LIMIT_EXCEEDED | 429 | Too many requests — slow down |
| RATE_EXPIRED | 400 | Rate quote has expired, refresh rates |
| EASYPOST_ERROR | 400 | Carrier returned an error |
| INTERNAL_ERROR | 500 | Unexpected server error — contact support |