{
  "info": {
    "_postman_id": "b3f1c2a4-6d5e-4f8a-9c1b-2e3d4f5a6b7c",
    "name": "MailHub Public API",
    "description": "MailHub API requests for authentication, Orders, Rates, and Labels. Set baseUrl to the URL supplied for your account and review each request before sending a state-changing operation. Try-It remains disabled in the portal; this collection is a separate manual-execution tool.",
    "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json"
  },
  "item": [
    {
      "name": "Authentication",
      "item": [
        {
          "name": "Create access token",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "// Stores the issued tokens as collection variables so later requests can use them.",
                  "// Never logs a token value.",
                  "if (pm.response && pm.response.code >= 200 && pm.response.code < 300) {",
                  "  const body = pm.response.json();",
                  "  if (body && body.success && body.data) {",
                  "    if (body.data.accessToken) pm.collectionVariables.set('accessToken', body.data.accessToken);",
                  "    if (body.data.refreshToken) pm.collectionVariables.set('refreshToken', body.data.refreshToken);",
                  "  }",
                  "}"
                ]
              }
            }
          ],
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json",
                "type": "text"
              },
              {
                "key": "X-SubAccount-Id",
                "value": "{{subAccountId}}",
                "type": "text",
                "disabled": true,
                "description": "Optional. Validates that the given sub-account exists and is active for your account (404/409 if not), but the result is not persisted to the issued token."
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"apiKey\": \"{{apiKey}}\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "url": {
              "raw": "{{baseUrl}}/api/v1/auth/token",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "api",
                "v1",
                "auth",
                "token"
              ]
            },
            "auth": {
              "type": "noauth"
            },
            "description": "Exchanges a Sandbox or Production API key for an access token and refresh token. Anonymous — does not require a bearer token. See /docs/getting-started/authentication."
          },
          "response": []
        },
        {
          "name": "Refresh access token",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "// Stores the issued tokens as collection variables so later requests can use them.",
                  "// Never logs a token value.",
                  "if (pm.response && pm.response.code >= 200 && pm.response.code < 300) {",
                  "  const body = pm.response.json();",
                  "  if (body && body.success && body.data) {",
                  "    if (body.data.accessToken) pm.collectionVariables.set('accessToken', body.data.accessToken);",
                  "    if (body.data.refreshToken) pm.collectionVariables.set('refreshToken', body.data.refreshToken);",
                  "  }",
                  "}"
                ]
              }
            }
          ],
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json",
                "type": "text"
              },
              {
                "key": "X-SubAccount-Id",
                "value": "{{subAccountId}}",
                "type": "text",
                "disabled": true,
                "description": "Optional. Not read by this operation; a malformed value still fails with 400 (checked globally)."
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"refreshToken\": \"{{refreshToken}}\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "url": {
              "raw": "{{baseUrl}}/api/v1/auth/token/refresh",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "api",
                "v1",
                "auth",
                "token",
                "refresh"
              ]
            },
            "auth": {
              "type": "noauth"
            },
            "description": "Rotates the refresh token: the token sent is invalidated and a new access/refresh pair is issued. Reusing an already-rotated refresh token fails with 401. See /docs/getting-started/authentication.\nThis request can create billable or persistent state. Review variables and environment before sending. Rotating invalidates the refresh token you send."
          },
          "response": []
        },
        {
          "name": "Revoke refresh token",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json",
                "type": "text"
              },
              {
                "key": "X-SubAccount-Id",
                "value": "{{subAccountId}}",
                "type": "text",
                "disabled": true,
                "description": "Optional. Not read by this operation; a malformed value still fails with 400 (checked globally)."
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"refreshToken\": \"{{refreshToken}}\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "url": {
              "raw": "{{baseUrl}}/api/v1/auth/token/revoke",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "api",
                "v1",
                "auth",
                "token",
                "revoke"
              ]
            },
            "auth": {
              "type": "noauth"
            },
            "description": "Ends a refresh session. Idempotent — revoking an already-revoked session still returns success. An already-issued access token keeps working until it naturally expires. See /docs/getting-started/authentication.\nThis request can create billable or persistent state. Review variables and environment before sending. This ends the refresh session tied to {{refreshToken}}."
          },
          "response": []
        }
      ]
    },
    {
      "name": "Orders",
      "item": [
        {
          "name": "Create order",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "// Stores the created order/shipment id as collection variables. Does not send any follow-up request.",
                  "if (pm.response && pm.response.code >= 200 && pm.response.code < 300) {",
                  "  const body = pm.response.json();",
                  "  if (body && body.success && body.data) {",
                  "    if (body.data.id) pm.collectionVariables.set('orderId', body.data.id);",
                  "    const shipment = Array.isArray(body.data.shipments) ? body.data.shipments[0] : undefined;",
                  "    if (shipment && shipment.id) pm.collectionVariables.set('shipmentId', shipment.id);",
                  "  }",
                  "}"
                ]
              }
            }
          ],
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json",
                "type": "text"
              },
              {
                "key": "X-SubAccount-Id",
                "value": "{{subAccountId}}",
                "type": "text",
                "disabled": true,
                "description": "Optional. If present, tags the newly created order (and its shipments) with this sub-account."
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"fromAddress\": {\n    \"name\": \"Jane Sender\",\n    \"street1\": \"123 Main St\",\n    \"city\": \"Austin\",\n    \"state\": \"TX\",\n    \"zip\": \"78701\",\n    \"country\": \"US\",\n    \"phone\": \"5125550100\"\n  },\n  \"toAddress\": {\n    \"name\": \"John Recipient\",\n    \"street1\": \"456 Oak Ave\",\n    \"city\": \"Denver\",\n    \"state\": \"CO\",\n    \"zip\": \"80202\",\n    \"country\": \"US\",\n    \"phone\": \"3035550100\"\n  },\n  \"shipments\": [\n    {\n      \"parcel\": {\n        \"weight\": 16,\n        \"length\": 10,\n        \"width\": 8,\n        \"height\": 4\n      }\n    }\n  ]\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "url": {
              "raw": "{{baseUrl}}/api/v1/orders",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "api",
                "v1",
                "orders"
              ]
            },
            "auth": {
              "type": "bearer",
              "bearer": [
                {
                  "key": "token",
                  "value": "{{accessToken}}",
                  "type": "string"
                }
              ]
            },
            "description": "Creates an Order and its Shipment(s). fromAddress, toAddress, and each shipment's parcel.weight are required; the rest are optional. See /docs/guides/orders/create-order.\nThis request can create billable or persistent state. Review variables and environment before sending."
          },
          "response": []
        },
        {
          "name": "List orders",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "X-SubAccount-Id",
                "value": "{{subAccountId}}",
                "type": "text",
                "disabled": true,
                "description": "Optional. If present, filters the returned orders to this sub-account only; omit to list all of the account's orders."
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/api/v1/orders",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "api",
                "v1",
                "orders"
              ],
              "query": [
                {
                  "key": "search",
                  "value": "",
                  "disabled": true,
                  "description": "Optional free-text search."
                },
                {
                  "key": "sortByNewest",
                  "value": "true",
                  "disabled": true,
                  "description": "Optional. Defaults are applied when omitted."
                },
                {
                  "key": "dateFrom",
                  "value": "",
                  "disabled": true,
                  "description": "Optional ISO 8601 UTC lower bound."
                },
                {
                  "key": "dateTo",
                  "value": "",
                  "disabled": true,
                  "description": "Optional ISO 8601 UTC upper bound."
                },
                {
                  "key": "statuses",
                  "value": "",
                  "disabled": true,
                  "description": "Optional repeated numeric OrderStatus filter (0-11)."
                },
                {
                  "key": "pageNumber",
                  "value": "1",
                  "disabled": true,
                  "description": "Optional. Defaults are applied when omitted."
                },
                {
                  "key": "pageSize",
                  "value": "20",
                  "disabled": true,
                  "description": "Optional. Defaults are applied when omitted."
                }
              ]
            },
            "auth": {
              "type": "bearer",
              "bearer": [
                {
                  "key": "token",
                  "value": "{{accessToken}}",
                  "type": "string"
                }
              ]
            },
            "description": "Lists Orders for your account. Every parameter is optional. See /docs/getting-started/first-request."
          },
          "response": []
        },
        {
          "name": "Get order",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "X-SubAccount-Id",
                "value": "{{subAccountId}}",
                "type": "text",
                "disabled": true,
                "description": "Optional. Optional but has no effect on this operation — looked up by account only."
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/api/v1/orders/{{orderId}}",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "api",
                "v1",
                "orders",
                "{{orderId}}"
              ]
            },
            "auth": {
              "type": "bearer",
              "bearer": [
                {
                  "key": "token",
                  "value": "{{accessToken}}",
                  "type": "string"
                }
              ]
            },
            "description": "Retrieves a single Order by id. See /docs/guides/orders."
          },
          "response": []
        },
        {
          "name": "Update order status",
          "request": {
            "method": "PATCH",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json",
                "type": "text"
              },
              {
                "key": "X-SubAccount-Id",
                "value": "{{subAccountId}}",
                "type": "text",
                "disabled": true,
                "description": "Optional. Optional but has no effect on this operation — looked up and updated by account only."
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"status\": 7,\n  \"reason\": \"Packed and ready to ship\",\n  \"source\": \"warehouse-scanner\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "url": {
              "raw": "{{baseUrl}}/api/v1/orders/{{orderId}}/status",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "api",
                "v1",
                "orders",
                "{{orderId}}",
                "status"
              ]
            },
            "auth": {
              "type": "bearer",
              "bearer": [
                {
                  "key": "token",
                  "value": "{{accessToken}}",
                  "type": "string"
                }
              ]
            },
            "description": "Moves an Order to a new numeric OrderStatus (0-11). Not every (current, target) pair is allowed — an unsupported transition returns 409. Packed (7) is not required before Rates or Label purchase. See /docs/guides/orders/update-order-status.\nThis request can create billable or persistent state. Review variables and environment before sending."
          },
          "response": []
        },
        {
          "name": "Create an Order with Rates",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "// Stores the created order/shipment id and, if a rate came back, the first rate id.",
                  "// Does not send any follow-up request.",
                  "if (pm.response && pm.response.code >= 200 && pm.response.code < 300) {",
                  "  const body = pm.response.json();",
                  "  const order = body && body.data && body.data.order;",
                  "  if (order && order.id) pm.collectionVariables.set('orderId', order.id);",
                  "  const shipment = order && Array.isArray(order.shipments) ? order.shipments[0] : undefined;",
                  "  if (shipment && shipment.id) pm.collectionVariables.set('shipmentId', shipment.id);",
                  "  const shipmentRates = body && body.data && Array.isArray(body.data.shipmentRates) ? body.data.shipmentRates[0] : undefined;",
                  "  const firstRate = shipmentRates && Array.isArray(shipmentRates.rates) ? shipmentRates.rates[0] : undefined;",
                  "  if (firstRate && firstRate.id) pm.collectionVariables.set('rateId', firstRate.id);",
                  "}"
                ]
              }
            }
          ],
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json",
                "type": "text"
              },
              {
                "key": "X-SubAccount-Id",
                "value": "{{subAccountId}}",
                "type": "text",
                "disabled": true,
                "description": "Optional. If present, tags the newly created order (and its shipments) with this sub-account — same behavior as Create order (v1)."
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"order\": {\n    \"fromAddress\": {\n      \"name\": \"Jane Sender\",\n      \"street1\": \"123 Main St\",\n      \"city\": \"Austin\",\n      \"state\": \"TX\",\n      \"zip\": \"78701\",\n      \"country\": \"US\",\n      \"phone\": \"5125550100\"\n    },\n    \"toAddress\": {\n      \"name\": \"John Recipient\",\n      \"street1\": \"456 Oak Ave\",\n      \"city\": \"Denver\",\n      \"state\": \"CO\",\n      \"zip\": \"80202\",\n      \"country\": \"US\",\n      \"phone\": \"3035550100\"\n    },\n    \"shipments\": [\n      {\n        \"parcel\": {\n          \"weight\": 16,\n          \"length\": 10,\n          \"width\": 8,\n          \"height\": 4\n        }\n      }\n    ]\n  }\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "url": {
              "raw": "{{baseUrl}}/api/v2/orders",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "api",
                "v2",
                "orders"
              ]
            },
            "auth": {
              "type": "bearer",
              "bearer": [
                {
                  "key": "token",
                  "value": "{{accessToken}}",
                  "type": "string"
                }
              ]
            },
            "description": "Creates one Order, then immediately requests Rates for each created Shipment. Rates are grouped by Shipment. This is one Order per request, not a batch operation; it does not select a Rate or purchase a Label. See /docs/guides/orders/create-order-with-rates.\nThis request can create billable or persistent state. Review variables and environment before sending. A no-Rate 422 may occur after Order persistence; do not blindly retry."
          },
          "response": []
        }
      ]
    },
    {
      "name": "Rates",
      "item": [
        {
          "name": "Get shipment rates",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "// Stores the first returned rate id as a collection variable. Does not send any follow-up request.",
                  "if (pm.response && pm.response.code >= 200 && pm.response.code < 300) {",
                  "  const body = pm.response.json();",
                  "  const rates = body && body.data && Array.isArray(body.data.rates) ? body.data.rates : [];",
                  "  if (rates[0] && rates[0].id) pm.collectionVariables.set('rateId', rates[0].id);",
                  "}"
                ]
              }
            }
          ],
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "X-SubAccount-Id",
                "value": "{{subAccountId}}",
                "type": "text",
                "disabled": true,
                "description": "Optional. Evaluates the request against the sub-account's carrier availability, which can narrow the carriers returned; does not change rate pricing and does not restrict which shipments can be rated."
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/api/v1/shipments/{{shipmentId}}/rates",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "api",
                "v1",
                "shipments",
                "{{shipmentId}}",
                "rates"
              ]
            },
            "auth": {
              "type": "bearer",
              "bearer": [
                {
                  "key": "token",
                  "value": "{{accessToken}}",
                  "type": "string"
                }
              ]
            },
            "description": "Requests carrier rates for a Shipment. No request body and no Order-status precondition. carrierResponses reports per-carrier success/failure. See /docs/guides/shipments-rates/get-rates."
          },
          "response": []
        }
      ]
    },
    {
      "name": "Labels",
      "item": [
        {
          "name": "Buy label",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "// Stores the purchased label id as a collection variable. Does not send any follow-up request.",
                  "pm.collectionVariables.unset('labelId');",
                  "if (pm.response && pm.response.code >= 200 && pm.response.code < 300) {",
                  "  const body = pm.response.json();",
                  "  const labelId = body && body.data && body.data.postageLabel && body.data.postageLabel.id;",
                  "  pm.test('Buy Label response contains a non-empty Label identifier', () => {",
                  "    pm.expect(labelId).to.be.a('string').and.not.empty;",
                  "  });",
                  "  if (typeof labelId === 'string' && labelId.length > 0) pm.collectionVariables.set('labelId', labelId);",
                  "}"
                ]
              }
            }
          ],
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json",
                "type": "text"
              },
              {
                "key": "X-SubAccount-Id",
                "value": "{{subAccountId}}",
                "type": "text",
                "disabled": true,
                "description": "Optional. Evaluates the purchase against the sub-account's carrier availability, which can narrow the carriers available; does not change the label's charged price and does not restrict which shipments/rates can be used."
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"rate\": {\n    \"id\": \"{{rateId}}\"\n  }\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "url": {
              "raw": "{{baseUrl}}/api/v1/shipments/{{shipmentId}}/labels",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "api",
                "v1",
                "shipments",
                "{{shipmentId}}",
                "labels"
              ]
            },
            "auth": {
              "type": "bearer",
              "bearer": [
                {
                  "key": "token",
                  "value": "{{accessToken}}",
                  "type": "string"
                }
              ]
            },
            "description": "Purchases a Label using a selected Rate id. A successful purchase automatically moves the Order to LabelCreated. See /docs/guides/labels/buy-label.\nThis request can create billable or persistent state. Review variables and environment before sending. Label purchase can involve real cost. A 202 means the purchase is still running — read the Order for the outcome. There is no idempotency key, but sending the same purchase request again replays its outcome rather than buying a second Label; see /docs/guides/labels/buy-label."
          },
          "response": []
        },
        {
          "name": "Download label",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "X-SubAccount-Id",
                "value": "{{subAccountId}}",
                "type": "text",
                "disabled": true,
                "description": "Optional. Optional but has no effect on this operation — looked up by account only."
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/api/v1/labels/{{labelId}}/download?format={{labelFormat}}",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "api",
                "v1",
                "labels",
                "{{labelId}}",
                "download"
              ],
              "query": [
                {
                  "key": "format",
                  "value": "{{labelFormat}}",
                  "description": "1 = PDF, 2 = ZPL 300 DPI, 3 = ZPL 203 DPI, 4 = ZPL 600 DPI. Omit to fall back to your account's configured label format."
                }
              ]
            },
            "auth": {
              "type": "bearer",
              "bearer": [
                {
                  "key": "token",
                  "value": "{{accessToken}}",
                  "type": "string"
                }
              ]
            },
            "description": "Downloads a purchased Label as a binary file, not the normal JSON envelope. A 202 response means the file is pending or still generating; no polling interval is defined. Use Postman's 'Send and Download' (or an equivalent manual save) rather than parsing the body as JSON. See /docs/guides/labels/download-label."
          },
          "response": []
        },
        {
          "name": "Cancel label",
          "request": {
            "method": "DELETE",
            "header": [
              {
                "key": "X-SubAccount-Id",
                "value": "{{subAccountId}}",
                "type": "text",
                "disabled": true,
                "description": "Optional. Optional but has no effect on this operation — looked up and cancelled by account only."
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/api/v1/labels/{{labelId}}",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "api",
                "v1",
                "labels",
                "{{labelId}}"
              ]
            },
            "auth": {
              "type": "bearer",
              "bearer": [
                {
                  "key": "token",
                  "value": "{{accessToken}}",
                  "type": "string"
                }
              ]
            },
            "description": "Requests cancellation of a purchased Label. Inspect data.isSuccess, and do not infer provider action, Order state, refund status, or repeat-request safety from the response. See /docs/guides/labels/cancel-label.\nThis request can create billable or persistent state. Review variables and environment before sending. Cancellation success does not guarantee an immediate financial refund."
          },
          "response": []
        }
      ]
    }
  ],
  "auth": {
    "type": "bearer",
    "bearer": [
      {
        "key": "token",
        "value": "{{accessToken}}",
        "type": "string"
      }
    ]
  },
  "variable": [
    {
      "key": "baseUrl",
      "value": "",
      "type": "string"
    },
    {
      "key": "apiKey",
      "value": "",
      "type": "string"
    },
    {
      "key": "accessToken",
      "value": "",
      "type": "string"
    },
    {
      "key": "refreshToken",
      "value": "",
      "type": "string"
    },
    {
      "key": "subAccountId",
      "value": "",
      "type": "string"
    },
    {
      "key": "orderId",
      "value": "",
      "type": "string"
    },
    {
      "key": "shipmentId",
      "value": "",
      "type": "string"
    },
    {
      "key": "rateId",
      "value": "",
      "type": "string"
    },
    {
      "key": "labelId",
      "value": "",
      "type": "string"
    },
    {
      "key": "labelFormat",
      "value": "1",
      "type": "string"
    }
  ]
}
