Skip to main content

API Conventions

Conventions that apply across the public Mailhub API.

Base URL and environments​

Mailhub publishes two customer environments, and this page is the canonical source for both base URLs:

EnvironmentAPI base URL
Sandboxhttps://api-sandbox.mailhub.com
Productionhttps://api.mailhub.com

Each value is an origin: no trailing slash, and no /api suffix. Every public route already begins with /api/v1 or /api/v2, so a request URL is the base plus the route — https://api.mailhub.com/api/v1/orders — and a base ending in /api would send /api/api/v1/orders.

Every example in this portal keeps the base URL in an environment variable rather than inlining a host, so the same snippet runs against either environment:

# Sandbox
export MAILHUB_API_BASE_URL=https://api-sandbox.mailhub.com

# Production
export MAILHUB_API_BASE_URL=https://api.mailhub.com

Credentials are per environment and are not interchangeable — see Prepare Your Mailhub Account. Sandbox is for non-production testing and Production for live usage; see Sandbox for what that environment does and does not establish, and Production Readiness.

Authentication​

Every operation requires:

Authorization: Bearer <access-token>

except the three token operations, which are anonymous and carry their credential in the request body instead:

OperationBearer required?Body credential
POST /api/v1/auth/tokenNoapiKey
POST /api/v1/auth/token/refreshNorefreshToken
POST /api/v1/auth/token/revokeNorefreshToken

Sending a bearer token to any of the three is neither required nor a substitute for the body credential.

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.

Status fields and enums​

Order status is always a number. Every place the public API reports or accepts an Order's fulfilment status, it does so as the numeric OrderStatus value 0–11: the Create Order responses (POST /api/v1/orders's data.status and POST /api/v2/orders's data.order.status), the list and retrieval responses, the statuses filter, and the PATCH /orders/{orderId}/status request together with its previousStatus and currentStatus. See Update Order Status for the full mapping.

The names tooling shows you are explanatory, not an alternate wire value. The contract publishes a name alongside each OrderStatus code, so the API Reference and a generated client can show Created rather than a bare 0. That name is a label for the number. It is never what the API sends, and it is never something you may send.

Do not conflate the status domains. Several unrelated concepts are spelled status on this API, and they do not share a vocabulary or a representation:

FieldConceptPublished values
Order statusOrder fulfilment lifecycleThe numeric OrderStatus, 0–11
Shipment statusCarrier tracking state, as a stringUnknown, PreTransit, InTransit, OutForDelivery, Delivered, AvailableForPickup, ReturnToSender, Failure, Cancelled, Error
data.status on POST /api/v2/ordersA fixed acknowledgement for the combined create-and-rate call. Not an Order statuscreated
Label-purchase operation status (the 202 response)The asynchronous operation's own stateClaimed, Processing, OutcomeUnknown
labelProcessingStatus, labelAssets[].statusLabel file rendering progresscompleted, processing
labelAssets[].formatWhich printable rendering the entry isPDF, ZPL300DPI, ZPL203DPI, ZPL600DPI
Label statusLabel lifecycleThe numeric LabelStatus, 0–4
category on a label-file errorBroad failure class the HTTP status derives fromGeneral, Validation, Authorization, NotFound, Conflict, Infrastructure, Upstream

Each of these vocabularies is published in the contract, so the API Reference, the Postman collection and a generated client all carry the permitted values. Two caveats on reading them:

  • A published vocabulary is what the field can carry, not what you will see today. Carrier tracking is not wired in, so a Shipment status reads Unknown in practice even though the full tracking vocabulary is declared.
  • The 202 vocabulary is a strict subset of the internal operation states. A completed purchase answers 200 and a confirmed failure answers an error, so neither of those states can appear on the 202 body.

Read the exact type the contract declares for the specific field you are reading, and do not translate one domain's values into another's.

Fields that carry a carrier's own vocabulary — serviceCode, serviceName, carrier, currency, billingType, mailClass, predefinedPackage — are deliberately not constrained. They have no closed value set Mailhub owns, and a carrier adding a service must not become a contract violation. Do not build against a fixed list of their values.

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.

Change policy​

The running Mailhub API is the source of truth for the public contract. What is published here — this portal, the API Reference, the OpenAPI document and the Postman collection — is generated from it, so a change to the API and a change to its documentation are the same change.

  • Customer-visible changes are described in Release Notes. When a release contains a breaking change, that release's notes identify it as breaking and name the operations affected.
  • A breaking correction does not automatically create a new major version. Where the published contract described something the API does not do, Mailhub may correct it in place under review rather than versioning the inaccuracy. Read the release notes for the release you are updating across.
  • Regenerated clients may need work after some changes. When a change to the OpenAPI document can alter generated types, method signatures, or required fields, the release notes say so.
  • Additive change is not breaking. A new response field, or a published set of permitted values for a field that previously declared none, does not require a new version. Parse responses tolerantly.

Deprecation and retirement​

Mailhub communicates planned API changes through this Developer Portal and Release Notes.

Mailhub does not currently guarantee a fixed minimum deprecation notice period, and does not currently guarantee a fixed overlap window during which a superseded major version keeps running alongside its replacement — a new major version may replace the previous one. Do not build a migration schedule on an assumed notice period.

If a version or an operation is deprecated, its concrete retirement timing is documented with that deprecation.

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/revokeRepeating a revoke for a token Mailhub has already revoked answers 200 again. Not blanket idempotency: an unrecognized token answers 401, and a missing, empty, or whitespace-only refreshToken answers 422
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. Individual response bodies may still carry diagnostic identifiers — a Label-purchase 202 returns operationId and correlationId — but no public operation accepts either value back. 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.