Skip to main content

API Conventions

Conventions that apply across the public Mailhub API.

Base URL and environments

No public hostname is approved yet. Use the environment variable convention already established in Getting Started and Make Your First Request:

${MAILHUB_API_BASE_URL}

Sandbox is for non-production testing and Production for live usage — obtain both base URLs and credentials from Mailhub separately for your Account. A customer-facing Sandbox's isolation, quotas, and non-billable behavior remain unconfirmed — do not assume Sandbox labels are free or automatically cleaned up. See Sandbox for what the environment does and does not establish, and Production Readiness.

Authentication

Every operation except token exchange (POST /api/v1/auth/token) requires:

Authorization: Bearer <access-token>

See Authentication for the full token exchange/refresh/revoke flow — this page does not repeat it. There is no public API-key lifecycle endpoint (creation, rotation, or listing) in the current boundary; key management happens through the Mailhub application (see Get an API Key).

Content types

  • Every request/response body is application/json except Download a Label, which returns application/pdf or application/zpl depending on the requested format. All three ZPL resolutions share the one ZPL media type.
  • Always inspect the HTTP status and Content-Type header of the download response before treating the body as either JSON or a label file — a 202/error response does not carry a label file at all.

Response envelope

Every JSON endpoint (auth, Orders, Rates, label purchase, and cancellation) returns the same envelope shape:

{
"success": true,
"data": { "...": "operation-specific" },
"errors": null
}

On failure, success is false, data is typically null, and errors is an array of:

{ "code": "...", "message": "...", "metaData": null }

POST /api/v2/orders keeps the outer response envelope. Its data contains order, shipmentRates, status, and operation-specific errors. See Create an Order with Rates. See Error Handling for status-code-by-status-code guidance.

IDs and paths

Resource IDs are passed as path parameters (/api/v1/orders/{orderId}, /api/v1/labels/{labelId}/download, …) and returned as opaque strings in response bodies — see Orders and Shipments for the identifier flow and why you should not parse or reuse them across environments.

Timestamps

Every timestamp field observed in the contract (createdAt, updatedAt, accessTokenExpiresAtUtc, refreshTokenExpiresAtUtc, …) is an ISO 8601 string in UTC — the Utc suffix on the token-expiry fields is explicit about this, and none of the other timestamp fields declare a different offset. Do not assume a local timezone.

Pagination and filtering

Only GET /api/v1/orders currently supports list filtering:

ParameterRequiredNotes
searchNoFree-text search
sortByNewestNoBoolean
dateFrom / dateToNoISO 8601 date-time
statusesNoArray of the numeric OrderStatus enum (see Object Model)
pageNumber / pageSizeNoDefaults to page 1, size 20

The response envelope carries items, totalItemsCount, pageNumber, pageSize, totalPages, hasNext, and hasPrevious — see Make Your First Request for a worked example. No other public list endpoint currently exists.

Enum casing

Enum values are not uniformly represented. Some appear as PascalCase strings (e.g. Order status on POST /orders's response: "Created"); the PATCH /orders/{orderId}/status endpoint instead represents the same concept as an integer (011). Do not normalize or translate either representation in your own code — read the exact type the contract declares for that field. See Update Order Status for the full mapping.

Versioning

  • /api/v1 is the current stable surface.
  • /api/v2 currently has one Order operation: POST /api/v2/orders, the combined create-and-rate flow. It creates one Order per request — see Create an Order with Rates.
  • No deprecation window or backward-compatibility guarantee beyond "breaking changes require a new version" is currently an accepted, published decision — do not assume a specific notice period.

Idempotency and retries

There is no global idempotency guarantee across the public API. Behavior is endpoint-specific:

OperationSafe to retry?
GET/list/retrieve operations, and Label downloadGenerally safe to retry after a transient failure
POST /api/v1/auth/token/refreshNot safely repeatable with the same refresh token — refresh rotates it; retrying with the now-spent token fails
POST /api/v1/auth/token/revokeDo not assume repeat safety beyond the response you receive
DELETE /api/v1/labels/{labelId} (cancel)Do not assume repeat safety beyond data.isSuccess
POST /api/v1/shipments/{shipmentId}/labels (purchase)No idempotency key, but a repeat of the same purchase request is recognized server-side and replays its outcome instead of buying a second Label
POST /api/v1/orders, POST /api/v2/orders, POST .../rates, PATCH .../statusNo documented idempotency key — do not blindly repeat an ambiguous result

See Retries and Ambiguous Outcomes for operation-specific ambiguity guidance.

Rate limiting

The public API does not currently declare a 429 response or a requests-per-minute quota. Do not depend on a specific limit or recovery behavior that is not documented.

Correlation and support evidence

The public API does not currently declare a request/correlation ID response header. When retaining evidence for Developer Support, record your own client-side timestamp, the operation and route template, the HTTP status, the sanitized errors body, and any Order/Shipment/Label identifiers involved — never a bearer token, refresh token, or full label file content.