CostDock/API Documentation

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/credits

Security: 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:

HeaderDescription
X-RateLimit-LimitRequests allowed per window
X-RateLimit-RemainingRequests remaining
X-RateLimit-ResetUnix timestamp when window resets
Retry-AfterSeconds 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/shipments

Endpoints

GET/api/v1/credits

Get your current credit balance.

Request

curl -H "Authorization: Bearer sk_live_..." \
  https://app.costdock.com/api/v1/credits

Response

{
  "success": true,
  "data": {
    "balance_cents": 5000,
    "formatted_balance": "$50.00"
  }
}
POST/api/v1/rates

Get 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/rates

Response

{
  "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).

POST/api/v1/addresses/validate

Validate 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/validate

Response

{
  "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
    }
  }
}
GET/api/v1/shipments?page=1&per_page=20

List 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
  }
}
POST/api/v1/shipments

Create 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/shipments

Response

{
  "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.

GET/api/v1/shipments/:id

Get details for a specific shipment.

Request

curl -H "Authorization: Bearer sk_live_..." \
  https://app.costdock.com/api/v1/shipments/uuid-here

Response

{
  "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"
  }
}
POST/api/v1/shipments/:id/void

Void 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/void

Response

{
  "success": true,
  "data": {
    "id": "uuid",
    "status": "voided",
    "refunded_amount_cents": 825
  }
}
GET/api/v1/tracking/:code

Get tracking status and events for a shipment.

Request

curl -H "Authorization: Bearer sk_live_..." \
  https://app.costdock.com/api/v1/tracking/1Z999AA10123456784

Response

{
  "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"
      }
    ]
  }
}
GET/api/v1/batches?page=1&per_page=20

List 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
  }
}
POST/api/v1/batches

Create 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/batches

Response

{
  "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.

GET/api/v1/batches/:id

Get details for a specific batch including its shipments.

Request

curl -H "Authorization: Bearer sk_live_..." \
  https://app.costdock.com/api/v1/batches/uuid-here

Response

{
  "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"
      }
    ]
  }
}
POST/api/v1/batches/:id/purchase

Purchase 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/purchase

Response

{
  "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

shipment.createdshipment.purchasedshipment.in_transitshipment.deliveredshipment.voidedshipment.returnedshipment.failedbatch.createdbatch.completed

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

GET/api/v1/webhooks?page=1&per_page=20

List 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
  }
}
POST/api/v1/webhooks

Create 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/webhooks

Response

{
  "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"
  }
}
GET/api/v1/webhooks/:id

Get a specific webhook endpoint including its signing key.

Request

curl -H "Authorization: Bearer sk_live_..." \
  https://app.costdock.com/api/v1/webhooks/uuid-here

Response

{
  "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"
  }
}
PATCH/api/v1/webhooks/:id

Update 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-here

Response

{
  "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"
  }
}
DELETE/api/v1/webhooks/:id

Delete 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-here

Response

{
  "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"}, 400

Important: 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.

AttemptDelay
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.

FieldRequiredFormatNotes
nameYes*String (max 100)Recipient or sender full name
companyNo*String (max 100)Company name — can substitute for name
street1YesString (max 200)Primary street address
street2NoString (max 200)Apt, suite, unit, floor
cityYesString (max 100)City
stateYes2-letter codeUS state abbreviation (e.g. CA, NY)
zipYes5-10 charsZIP or ZIP+4 (e.g. 94102, 94102-1234)
countryYes2-letter codeCountry code (US)
phoneNoString (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.

FieldRequiredValid ValuesDefaultNotes
packageTypeNo
packageenvelope
packageenvelope uses standard flat dims (11x6x0.25")
weightLbsYes*
Number (max 150)
Weight in pounds (recommended)
weightOzYes*
Number (max 2400)
Weight in ounces — backwards compatible, takes precedence if both provided
lengthInIf package
Number (max 108)
Length in inches
widthInIf package
Number (max 108)
Width in inches
heightInIf package
Number (max 108)
Height in inches
deliveryOptionNo
nonesignatureadult_signature
noneSignature confirmation level
labelFormatNo
PNGPDFZPL
PNGZPL 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.

CarrierCommon ServicesVoid 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:

StatusDescriptionTerminal?
quotedRate quote generated, label not yet purchasedNo
purchasedLabel purchased, awaiting carrier pickupNo
in_transitPackage picked up and moving through carrier networkNo
deliveredPackage delivered to recipientYes
voidedLabel voided and credits refundedYes
returnedPackage returned to senderYes
rejectedLabel rejected by adminYes
failedLabel purchase or delivery failedYes

Error Codes

CodeStatusDescription
UNAUTHORIZED401Invalid or missing API key
INVALID_REQUEST400/422Request validation failed (422 for address validation errors)
INSUFFICIENT_CREDITS402Not enough credits for this operation
AUTOPAY_FAILED403Autopay payment failed, add credits manually
NOT_FOUND404Resource not found
CONFLICT409Duplicate request or idempotency conflict
PAYLOAD_TOO_LARGE413Request body exceeds size limit
LIMIT_EXCEEDED422Resource limit reached (e.g. max webhooks)
RATE_LIMIT_EXCEEDED429Too many requests — slow down
RATE_EXPIRED400Rate quote has expired, refresh rates
EASYPOST_ERROR400Carrier returned an error
INTERNAL_ERROR500Unexpected server error — contact support