Skip to content
BoxmileBoxmileDevelopers
Get API access

API reference 1.0.0-alpha.1

Boxmile Public API

The Boxmile API for your own integrations: rate shipments, track parcels, check addresses and read your balance, with an API key. JSON fields are lowerCamelCase; money is integer US cents in ...Cents fields; timestamps are RFC 3339 UTC. Errors are RFC 9457 problem+json with a stable code. Every answer carries X-Request-Id, and every keyed answer the IETF RateLimit-Policy and RateLimit headers. Within v1, changes are additive only.

The API follows REST. Requests and answers are JSON over HTTPS, and every answer carries an X-Request-Id to quote when you contact us.

Base URL
https://api.boxmile.com
Your first request
curl https://api.boxmile.com/v1/services \
  -H "Authorization: Bearer $BOXMILE_API_KEY"
const response = await fetch('https://api.boxmile.com/v1/services', {
  headers: {
    Authorization: `Bearer ${process.env.BOXMILE_API_KEY}`,
  },
});
const data = await response.json();
console.log(response.status, data);
import os

import requests

response = requests.get(
    "https://api.boxmile.com/v1/services",
    headers={"Authorization": f"Bearer {os.environ['BOXMILE_API_KEY']}"},
    timeout=30,
)
print(response.status_code, response.json())

Authentication

Send your API key as a bearer token: Authorization: Bearer <your API key>. A key belongs to one workspace, and every request acts for that workspace only. To get a key, ask us for API access.

  • boxmile_sk_live_…Live keys act on real data and money.
  • boxmile_sk_test_…Test keys are read-only.

Keep keys on your server, never in a browser or an app, and read them from the environment as the samples do with BOXMILE_API_KEY.

Scopes

Each endpoint names the scope its key needs. A key with * has every scope.

  • rates.read

    The service catalog and your rate requests.

  • rates.write

    Rate a parcel. Nothing is bought.

  • labels.read

    Tracking.

  • addresses.read

    Address validation.

  • balance.read

    Your balance.

A missing, malformed, unknown, revoked or expired key gets 401. A key without the scope, or whose workspace is not active, gets 403. Refused keys count against a limit of 30 a minute for each client address.

Authenticated request
curl https://api.boxmile.com/v1/balance \
  -H "Authorization: Bearer $BOXMILE_API_KEY"

Errors

Errors are RFC 9457 problem details (application/problem+json). Branch on status and the stable code; title is plain text for people and is translated. instance repeats the request ID.

  • 400

    request.invalid

  • 401

    authentication.required

  • 403

    access.denied

  • 404

    resource.not_found

  • 429

    api.rate_limited

  • 503

    service.unavailable

Problem
{
  "type": "about:blank",
  "title": "Check the request and try again.",
  "status": 400,
  "code": "request.invalid",
  "params": {},
  "locale": "en",
  "instance": "urn:boxmile:request:0192f7a0-5c1e-7d3a-9b2e-4f6a8c0d1e2f"
}

Rate limits

Each key draws from buckets that refill over time. Every keyed answer carries the IETF RateLimit-Policy header (the bucket, its burst q and the seconds w it takes to refill) and RateLimit (requests r left now and seconds t until it is full).

An empty bucket answers 429 with Retry-After: wait that many seconds, then try again.

Headers
HTTP/1.1 200 OK
X-Request-Id: 0192f7a0-5c1e-7d3a-9b2e-4f6a8c0d1e2f
RateLimit-Policy: "reads";q=50;w=2
RateLimit: "reads";r=49;t=1

Versioning

The version is the first part of every path. Within it, changes are additive only: new endpoints, new optional fields and new values. Ignore fields you do not know, so a new one never breaks your integration. JSON fields are lowerCamelCase, money is integer US cents in fields ending in Cents, and times are RFC 3339 in UTC.

Use it in Postman

The whole API is one Postman collection, kept in step with this reference.

  1. Download the collection below.
  2. In Postman, choose Import and drop in the file. It needs no environment file.
  3. Open the collection’s Variables tab. baseUrl is already set. Paste your API key into apiKey’s Current value only: Postman keeps current values on your machine, so the key is never shared or exported. Never put a key in an initial value, a request or a file.
  4. Open Catalog › List the carrier services a key can rate and press Send. Then try Rates › Rate a parcel.
  5. Each request’s documentation gives its scope, parameters, body fields and errors, and its Examples list holds a saved answer for every documented status.

Catalog

This API's own document and the carrier services a key can rate.

GET/v1/openapi

Read this API's OpenAPI document

The reviewed OpenAPI 3.1 document of this API. It needs no key; reads count against a limit per client address.

No key needed

Responses

  • 200

    The OpenAPI document.

  • 429

    The bucket is empty. Wait Retry-After seconds.

    api.rate_limited

GET /v1/openapi
curl https://api.boxmile.com/v1/openapi
const response = await fetch('https://api.boxmile.com/v1/openapi');
const data = await response.json();
console.log(response.status, data);
import requests

response = requests.get(
    "https://api.boxmile.com/v1/openapi",
    timeout=30,
)
print(response.status_code, response.json())
Response 200
{
  "openapi": "3.1.0",
  "info": {
    "title": "Boxmile Public API",
    "version": "1.0.0"
  }
}
429api.rate_limited
Response 429
{
  "type": "about:blank",
  "title": "Too many requests. Wait the number of seconds in the Retry-After header, then try again.",
  "status": 429,
  "code": "api.rate_limited",
  "params": {},
  "locale": "en",
  "instance": "urn:boxmile:request:0192f7a0-5c1e-7d3a-9b2e-4f6a8c0d1e2f"
}

GET/v1/services

List the carrier services a key can rate

Every carrier service this deployment offers, in its configured order. Services name the carrier and the carrier's service, never the account or partner that fulfils it. The list has no further page.

Key scope rates.read

Responses

  • 200

    The services.

  • 401

    The key is missing, malformed, unknown, revoked or expired.

    authentication.required

  • 403

    The key lacks this operation's scope, or its workspace is not active.

    access.denied

  • 429

    The bucket is empty. Wait Retry-After seconds.

    api.rate_limited

  • 503

    Rating is not configured on this deployment.

    service.unavailable

GET /v1/services
curl https://api.boxmile.com/v1/services \
  -H "Authorization: Bearer $BOXMILE_API_KEY"
const response = await fetch('https://api.boxmile.com/v1/services', {
  headers: {
    Authorization: `Bearer ${process.env.BOXMILE_API_KEY}`,
  },
});
const data = await response.json();
console.log(response.status, data);
import os

import requests

response = requests.get(
    "https://api.boxmile.com/v1/services",
    headers={"Authorization": f"Bearer {os.environ['BOXMILE_API_KEY']}"},
    timeout=30,
)
print(response.status_code, response.json())
Response 200
{
  "object": "list",
  "items": [
    {
      "object": "service",
      "code": "USPS_GROUND_ADVANTAGE",
      "carrier": "usps",
      "carrierName": "USPS",
      "serviceName": "USPS Ground Advantage"
    }
  ],
  "nextCursor": null
}
401authentication.required
Response 401
{
  "type": "about:blank",
  "title": "Sign in to continue.",
  "status": 401,
  "code": "authentication.required",
  "params": {},
  "locale": "en",
  "instance": "urn:boxmile:request:0192f7a0-5c1e-7d3a-9b2e-4f6a8c0d1e2f"
}
403access.denied
Response 403
{
  "type": "about:blank",
  "title": "You do not have permission to perform this action.",
  "status": 403,
  "code": "access.denied",
  "params": {},
  "locale": "en",
  "instance": "urn:boxmile:request:0192f7a0-5c1e-7d3a-9b2e-4f6a8c0d1e2f"
}
429api.rate_limited
Response 429
{
  "type": "about:blank",
  "title": "Too many requests. Wait the number of seconds in the Retry-After header, then try again.",
  "status": 429,
  "code": "api.rate_limited",
  "params": {},
  "locale": "en",
  "instance": "urn:boxmile:request:0192f7a0-5c1e-7d3a-9b2e-4f6a8c0d1e2f"
}
503service.unavailable
Response 503
{
  "type": "about:blank",
  "title": "This feature is not available yet. Try again later.",
  "status": 503,
  "code": "service.unavailable",
  "params": {},
  "locale": "en",
  "instance": "urn:boxmile:request:0192f7a0-5c1e-7d3a-9b2e-4f6a8c0d1e2f"
}

Rates

Prices for a parcel from the same pipeline as the web app. Nothing is bought.

POST/v1/rates

Rate a parcel

Rates one parcel to one destination, from fromAddress or else your workspace's default origin, for the services named (default: every service in GET /v1/services). Each price comes from the same rating and pricing pipeline as the Boxmile web app, and nothing is bought. The server waits a few seconds: a finished request answers 201, one still rating answers 202 with a Location to poll. A service with no price is listed under unavailable with a reason. Each rate's id is the quote, and its price holds until expiresAt (15 minutes). Test keys can rate.

Key scope rates.write

Request body

  • toAddressAddressRequired

    A postal address. Give postalCode, or city and state. A destination without line1 is rated at a placeholder street, which does not change a price.

    Show child attributesHide child attributes
    • namestring

      Defaults to Recipient (destination) or Shipping Dept (origin).

      up to 200 characters

    • companystring

      Company name.

      up to 200 characters

    • line1string

      Street line 1.

      up to 200 characters

    • line2string

      Street line 2.

      up to 200 characters

    • citystring

      City or town.

      up to 200 characters

    • statestring

      State, province or region code.

      up to 200 characters

    • postalCodestring

      ZIP or postal code.

      up to 200 characters

    • countrystring

      ISO 3166-1 alpha-2 country code.

      pattern ^[A-Za-z]{2}$, default US

    • phonestring

      Phone number.

      up to 200 characters

    • emailstring

      Email address.

      up to 200 characters

    • residentialboolean or null

      Whether the address is a residence.

      default false

  • fromAddressAddress or null

    Leave out to ship from your workspace's default origin.

    Show child attributesHide child attributes
    • namestring

      Defaults to Recipient (destination) or Shipping Dept (origin).

      up to 200 characters

    • companystring

      Company name.

      up to 200 characters

    • line1string

      Street line 1.

      up to 200 characters

    • line2string

      Street line 2.

      up to 200 characters

    • citystring

      City or town.

      up to 200 characters

    • statestring

      State, province or region code.

      up to 200 characters

    • postalCodestring

      ZIP or postal code.

      up to 200 characters

    • countrystring

      ISO 3166-1 alpha-2 country code.

      pattern ^[A-Za-z]{2}$, default US

    • phonestring

      Phone number.

      up to 200 characters

    • emailstring

      Email address.

      up to 200 characters

    • residentialboolean or null

      Whether the address is a residence.

      default false

  • parcelParcelRequired
    Show child attributesHide child attributes
    • weightOznumberRequired

      Weight in ounces.

      more than 0, at most 2400

    • lengthInnumber

      Length in inches.

      more than 0, at most 120

    • widthInnumber

      Width in inches.

      more than 0, at most 120

    • heightInnumber

      Height in inches.

      more than 0, at most 120

  • servicesarray of string or null

    Service codes from GET /v1/services. Leave out to rate every service.

    1 to 64 items, no repeats

Responses

  • 201

    Rated. Location is this rate request.

  • 202

    Still rating after the server's wait (a few seconds). Poll Location; Retry-After is a hint in seconds.

  • 400

    The body is not valid JSON, has a field this API does not know, misses a required field, has a value out of range, or names a service the catalog does not offer.

    request.invalid

  • 401

    The key is missing, malformed, unknown, revoked or expired.

    authentication.required

  • 403

    The key lacks this operation's scope, or its workspace is not active.

    access.denied

  • 429

    The bucket is empty. Wait Retry-After seconds.

    api.rate_limited

  • 503

    Rating is not configured on this deployment.

    service.unavailable

POST /v1/rates
curl https://api.boxmile.com/v1/rates \
  -X POST \
  -H "Authorization: Bearer $BOXMILE_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "toAddress": {
    "name": "Ada Lovelace",
    "line1": "34 Example Road",
    "city": "New York",
    "state": "NY",
    "postalCode": "10001",
    "country": "US",
    "residential": true
  },
  "parcel": {
    "weightOz": 12,
    "lengthIn": 6,
    "widthIn": 4,
    "heightIn": 2
  },
  "services": [
    "USPS_GROUND_ADVANTAGE",
    "UPS_GROUND"
  ]
}'
const response = await fetch('https://api.boxmile.com/v1/rates', {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${process.env.BOXMILE_API_KEY}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    toAddress: {
      name: 'Ada Lovelace',
      line1: '34 Example Road',
      city: 'New York',
      state: 'NY',
      postalCode: '10001',
      country: 'US',
      residential: true,
    },
    parcel: {
      weightOz: 12,
      lengthIn: 6,
      widthIn: 4,
      heightIn: 2,
    },
    services: [
      'USPS_GROUND_ADVANTAGE',
      'UPS_GROUND',
    ],
  }),
});
const data = await response.json();
console.log(response.status, data);
import os

import requests

response = requests.post(
    "https://api.boxmile.com/v1/rates",
    headers={"Authorization": f"Bearer {os.environ['BOXMILE_API_KEY']}"},
    json={
        "toAddress": {
            "name": "Ada Lovelace",
            "line1": "34 Example Road",
            "city": "New York",
            "state": "NY",
            "postalCode": "10001",
            "country": "US",
            "residential": True,
        },
        "parcel": {
            "weightOz": 12,
            "lengthIn": 6,
            "widthIn": 4,
            "heightIn": 2,
        },
        "services": [
            "USPS_GROUND_ADVANTAGE",
            "UPS_GROUND",
        ],
    },
    timeout=30,
)
print(response.status_code, response.json())
Response 201
{
  "object": "rate_request",
  "id": "0192f7a0-5c1e-7d3a-9b2e-4f6a8c0d1e2f",
  "status": "completed",
  "failureReason": null,
  "createdAt": "2026-10-20T03:00:00Z",
  "rates": [
    {
      "object": "rate",
      "id": "0192f7a0-5c1e-7d3a-9b2e-4f6a8c0d1e01",
      "service": "USPS_GROUND_ADVANTAGE",
      "carrier": "usps",
      "carrierName": "USPS",
      "serviceName": "USPS Ground Advantage",
      "amountCents": 725,
      "currency": "usd",
      "expiresAt": "2026-10-20T03:15:00Z"
    }
  ],
  "unavailable": [
    {
      "service": "UPS_GROUND",
      "reason": "no_rate"
    }
  ]
}
202Still rating after the server's wait (a few seconds). Poll Location; Retry-After is a hint in seconds.
Response 202
{
  "object": "rate_request",
  "id": "0192f7a0-5c1e-7d3a-9b2e-4f6a8c0d1e2f",
  "status": "pending",
  "failureReason": null,
  "createdAt": "2026-10-20T03:00:00Z",
  "rates": [],
  "unavailable": []
}
400request.invalid
Response 400
{
  "type": "about:blank",
  "title": "Check the request and try again.",
  "status": 400,
  "code": "request.invalid",
  "params": {},
  "locale": "en",
  "instance": "urn:boxmile:request:0192f7a0-5c1e-7d3a-9b2e-4f6a8c0d1e2f"
}
401authentication.required
Response 401
{
  "type": "about:blank",
  "title": "Sign in to continue.",
  "status": 401,
  "code": "authentication.required",
  "params": {},
  "locale": "en",
  "instance": "urn:boxmile:request:0192f7a0-5c1e-7d3a-9b2e-4f6a8c0d1e2f"
}
403access.denied
Response 403
{
  "type": "about:blank",
  "title": "You do not have permission to perform this action.",
  "status": 403,
  "code": "access.denied",
  "params": {},
  "locale": "en",
  "instance": "urn:boxmile:request:0192f7a0-5c1e-7d3a-9b2e-4f6a8c0d1e2f"
}
429api.rate_limited
Response 429
{
  "type": "about:blank",
  "title": "Too many requests. Wait the number of seconds in the Retry-After header, then try again.",
  "status": 429,
  "code": "api.rate_limited",
  "params": {},
  "locale": "en",
  "instance": "urn:boxmile:request:0192f7a0-5c1e-7d3a-9b2e-4f6a8c0d1e2f"
}
503service.unavailable
Response 503
{
  "type": "about:blank",
  "title": "This feature is not available yet. Try again later.",
  "status": 503,
  "code": "service.unavailable",
  "params": {},
  "locale": "en",
  "instance": "urn:boxmile:request:0192f7a0-5c1e-7d3a-9b2e-4f6a8c0d1e2f"
}

GET/v1/rate-requests/{id}

Read a rate request

A rate request this API key made, as it stands now; another key, even of the same workspace, gets 404. While status is pending, poll again after Retry-After seconds.

Key scope rates.read

Path parameters

  • idstringRequired

    The rate request's id.

    uuid

Responses

  • 200

    The rate request.

  • 400

    The id is not a UUID.

    request.invalid

  • 401

    The key is missing, malformed, unknown, revoked or expired.

    authentication.required

  • 403

    The key lacks this operation's scope, or its workspace is not active.

    access.denied

  • 404

    No rate request with this id was made by your workspace's API keys.

    resource.not_found

  • 429

    The bucket is empty. Wait Retry-After seconds.

    api.rate_limited

  • 503

    Rating is not configured on this deployment.

    service.unavailable

GET /v1/rate-requests/{id}
curl https://api.boxmile.com/v1/rate-requests/0192f7a0-5c1e-7d3a-9b2e-4f6a8c0d1e2f \
  -H "Authorization: Bearer $BOXMILE_API_KEY"
const response = await fetch('https://api.boxmile.com/v1/rate-requests/0192f7a0-5c1e-7d3a-9b2e-4f6a8c0d1e2f', {
  headers: {
    Authorization: `Bearer ${process.env.BOXMILE_API_KEY}`,
  },
});
const data = await response.json();
console.log(response.status, data);
import os

import requests

response = requests.get(
    "https://api.boxmile.com/v1/rate-requests/0192f7a0-5c1e-7d3a-9b2e-4f6a8c0d1e2f",
    headers={"Authorization": f"Bearer {os.environ['BOXMILE_API_KEY']}"},
    timeout=30,
)
print(response.status_code, response.json())
Response 200
{
  "object": "rate_request",
  "id": "0192f7a0-5c1e-7d3a-9b2e-4f6a8c0d1e2f",
  "status": "completed",
  "failureReason": null,
  "createdAt": "2026-10-20T03:00:00Z",
  "rates": [
    {
      "object": "rate",
      "id": "0192f7a0-5c1e-7d3a-9b2e-4f6a8c0d1e01",
      "service": "USPS_GROUND_ADVANTAGE",
      "carrier": "usps",
      "carrierName": "USPS",
      "serviceName": "USPS Ground Advantage",
      "amountCents": 725,
      "currency": "usd",
      "expiresAt": "2026-10-20T03:15:00Z"
    }
  ],
  "unavailable": [
    {
      "service": "UPS_GROUND",
      "reason": "no_rate"
    }
  ]
}
400request.invalid
Response 400
{
  "type": "about:blank",
  "title": "Check the request and try again.",
  "status": 400,
  "code": "request.invalid",
  "params": {},
  "locale": "en",
  "instance": "urn:boxmile:request:0192f7a0-5c1e-7d3a-9b2e-4f6a8c0d1e2f"
}
401authentication.required
Response 401
{
  "type": "about:blank",
  "title": "Sign in to continue.",
  "status": 401,
  "code": "authentication.required",
  "params": {},
  "locale": "en",
  "instance": "urn:boxmile:request:0192f7a0-5c1e-7d3a-9b2e-4f6a8c0d1e2f"
}
403access.denied
Response 403
{
  "type": "about:blank",
  "title": "You do not have permission to perform this action.",
  "status": 403,
  "code": "access.denied",
  "params": {},
  "locale": "en",
  "instance": "urn:boxmile:request:0192f7a0-5c1e-7d3a-9b2e-4f6a8c0d1e2f"
}
404resource.not_found
Response 404
{
  "type": "about:blank",
  "title": "The requested resource was not found.",
  "status": 404,
  "code": "resource.not_found",
  "params": {},
  "locale": "en",
  "instance": "urn:boxmile:request:0192f7a0-5c1e-7d3a-9b2e-4f6a8c0d1e2f"
}
429api.rate_limited
Response 429
{
  "type": "about:blank",
  "title": "Too many requests. Wait the number of seconds in the Retry-After header, then try again.",
  "status": 429,
  "code": "api.rate_limited",
  "params": {},
  "locale": "en",
  "instance": "urn:boxmile:request:0192f7a0-5c1e-7d3a-9b2e-4f6a8c0d1e2f"
}
503service.unavailable
Response 503
{
  "type": "about:blank",
  "title": "This feature is not available yet. Try again later.",
  "status": 503,
  "code": "service.unavailable",
  "params": {},
  "locale": "en",
  "instance": "urn:boxmile:request:0192f7a0-5c1e-7d3a-9b2e-4f6a8c0d1e2f"
}

Tracking

Delivery status and carrier scans of your workspace's parcels.

GET/v1/tracking/{trackingNumber}

Track a parcel

The delivery status and carrier scans, newest first, of the parcel with this tracking number that your workspace shipped. A parcel of another workspace, a voided label and an unknown number all answer 404. Test keys can track.

Key scope labels.read

Path parameters

  • trackingNumberstringRequired

    The carrier tracking number, exactly as on the label (letters and digits).

    pattern ^[A-Za-z0-9]{4,64}$

Responses

  • 200

    The parcel's tracking.

  • 400

    The tracking number is not 4 to 64 letters and digits.

    request.invalid

  • 401

    The key is missing, malformed, unknown, revoked or expired.

    authentication.required

  • 403

    The key lacks this operation's scope, or its workspace is not active.

    access.denied

  • 404

    Your workspace has no live parcel with this tracking number.

    resource.not_found

  • 429

    The bucket is empty. Wait Retry-After seconds.

    api.rate_limited

  • 503

    Tracking is not configured on this deployment, or its store is busy. Try again later.

    service.unavailable

GET /v1/tracking/{trackingNumber}
curl https://api.boxmile.com/v1/tracking/1Z999AA10123456784 \
  -H "Authorization: Bearer $BOXMILE_API_KEY"
const response = await fetch('https://api.boxmile.com/v1/tracking/1Z999AA10123456784', {
  headers: {
    Authorization: `Bearer ${process.env.BOXMILE_API_KEY}`,
  },
});
const data = await response.json();
console.log(response.status, data);
import os

import requests

response = requests.get(
    "https://api.boxmile.com/v1/tracking/1Z999AA10123456784",
    headers={"Authorization": f"Bearer {os.environ['BOXMILE_API_KEY']}"},
    timeout=30,
)
print(response.status_code, response.json())
Response 200
{
  "object": "tracking",
  "trackingNumber": "1Z999AA10123456784",
  "carrier": "ups",
  "carrierName": "UPS",
  "status": "in_transit",
  "events": [
    {
      "id": "0192f7a0-5c1e-7d3a-9b2e-4f6a8c0d1e10",
      "occurredAt": "2026-10-20T02:10:00Z",
      "status": "In transit",
      "description": "Departed facility",
      "location": "Louisville, KY"
    }
  ]
}
400request.invalid
Response 400
{
  "type": "about:blank",
  "title": "Check the request and try again.",
  "status": 400,
  "code": "request.invalid",
  "params": {},
  "locale": "en",
  "instance": "urn:boxmile:request:0192f7a0-5c1e-7d3a-9b2e-4f6a8c0d1e2f"
}
401authentication.required
Response 401
{
  "type": "about:blank",
  "title": "Sign in to continue.",
  "status": 401,
  "code": "authentication.required",
  "params": {},
  "locale": "en",
  "instance": "urn:boxmile:request:0192f7a0-5c1e-7d3a-9b2e-4f6a8c0d1e2f"
}
403access.denied
Response 403
{
  "type": "about:blank",
  "title": "You do not have permission to perform this action.",
  "status": 403,
  "code": "access.denied",
  "params": {},
  "locale": "en",
  "instance": "urn:boxmile:request:0192f7a0-5c1e-7d3a-9b2e-4f6a8c0d1e2f"
}
404resource.not_found
Response 404
{
  "type": "about:blank",
  "title": "The requested resource was not found.",
  "status": 404,
  "code": "resource.not_found",
  "params": {},
  "locale": "en",
  "instance": "urn:boxmile:request:0192f7a0-5c1e-7d3a-9b2e-4f6a8c0d1e2f"
}
429api.rate_limited
Response 429
{
  "type": "about:blank",
  "title": "Too many requests. Wait the number of seconds in the Retry-After header, then try again.",
  "status": 429,
  "code": "api.rate_limited",
  "params": {},
  "locale": "en",
  "instance": "urn:boxmile:request:0192f7a0-5c1e-7d3a-9b2e-4f6a8c0d1e2f"
}
503service.unavailable
Response 503
{
  "type": "about:blank",
  "title": "This feature is not available yet. Try again later.",
  "status": 503,
  "code": "service.unavailable",
  "params": {},
  "locale": "en",
  "instance": "urn:boxmile:request:0192f7a0-5c1e-7d3a-9b2e-4f6a8c0d1e2f"
}

Addresses

Address checks. Nothing is stored and no carrier is asked.

POST/v1/addresses/validate

Validate an address

Checks one address's form and answers it standardized. Every address needs line1 and city, and a country Boxmile recognizes and may ship to (sanctioned countries and regions are refused). A US-domestic address (US and the USPS-domestic territories) also needs a USPS state, by code or name, and a 5- or 9-digit ZIP. The state becomes its code, a 9-digit ZIP is written 12345-6789 and runs of spaces become one. No carrier is asked, so carrierVerified is false: a valid address is unverified, or standardized when its form changed. An address that fails a check is a 200 with status invalid and each field's reason. Nothing is stored. Test keys can validate.

Key scope addresses.read

Request body

  • namestring

    Defaults to Recipient (destination) or Shipping Dept (origin).

    up to 200 characters

  • companystring

    Company name.

    up to 200 characters

  • line1string

    Street line 1.

    up to 200 characters

  • line2string

    Street line 2.

    up to 200 characters

  • citystring

    City or town.

    up to 200 characters

  • statestring

    State, province or region code.

    up to 200 characters

  • postalCodestring

    ZIP or postal code.

    up to 200 characters

  • countrystring

    ISO 3166-1 alpha-2 country code.

    pattern ^[A-Za-z]{2}$, default US

  • phonestring

    Phone number.

    up to 200 characters

  • emailstring

    Email address.

    up to 200 characters

  • residentialboolean or null

    Whether the address is a residence.

    default false

Responses

  • 200

    The address's validation.

  • 400

    The body is not an Address: not JSON, an unknown field, a value that is not text, text over 200 characters, or a country that is not two letters.

    request.invalid

  • 401

    The key is missing, malformed, unknown, revoked or expired.

    authentication.required

  • 403

    The key lacks this operation's scope, or its workspace is not active.

    access.denied

  • 429

    The bucket is empty. Wait Retry-After seconds.

    api.rate_limited

POST /v1/addresses/validate
curl https://api.boxmile.com/v1/addresses/validate \
  -X POST \
  -H "Authorization: Bearer $BOXMILE_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "line1": "34 Example Road",
  "city": "New York",
  "state": "new york",
  "postalCode": "100011234"
}'
const response = await fetch('https://api.boxmile.com/v1/addresses/validate', {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${process.env.BOXMILE_API_KEY}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    line1: '34 Example Road',
    city: 'New York',
    state: 'new york',
    postalCode: '100011234',
  }),
});
const data = await response.json();
console.log(response.status, data);
import os

import requests

response = requests.post(
    "https://api.boxmile.com/v1/addresses/validate",
    headers={"Authorization": f"Bearer {os.environ['BOXMILE_API_KEY']}"},
    json={
        "line1": "34 Example Road",
        "city": "New York",
        "state": "new york",
        "postalCode": "100011234",
    },
    timeout=30,
)
print(response.status_code, response.json())
Response 200
{
  "object": "address_validation",
  "status": "standardized",
  "carrierVerified": false,
  "address": {
    "line1": "34 Example Road",
    "line2": null,
    "city": "New York",
    "state": "NY",
    "postalCode": "10001-1234",
    "country": "US"
  },
  "errors": [],
  "notices": [
    "review_formatting",
    "not_verified"
  ]
}
400request.invalid
Response 400
{
  "type": "about:blank",
  "title": "Check the request and try again.",
  "status": 400,
  "code": "request.invalid",
  "params": {},
  "locale": "en",
  "instance": "urn:boxmile:request:0192f7a0-5c1e-7d3a-9b2e-4f6a8c0d1e2f"
}
401authentication.required
Response 401
{
  "type": "about:blank",
  "title": "Sign in to continue.",
  "status": 401,
  "code": "authentication.required",
  "params": {},
  "locale": "en",
  "instance": "urn:boxmile:request:0192f7a0-5c1e-7d3a-9b2e-4f6a8c0d1e2f"
}
403access.denied
Response 403
{
  "type": "about:blank",
  "title": "You do not have permission to perform this action.",
  "status": 403,
  "code": "access.denied",
  "params": {},
  "locale": "en",
  "instance": "urn:boxmile:request:0192f7a0-5c1e-7d3a-9b2e-4f6a8c0d1e2f"
}
429api.rate_limited
Response 429
{
  "type": "about:blank",
  "title": "Too many requests. Wait the number of seconds in the Retry-After header, then try again.",
  "status": 429,
  "code": "api.rate_limited",
  "params": {},
  "locale": "en",
  "instance": "urn:boxmile:request:0192f7a0-5c1e-7d3a-9b2e-4f6a8c0d1e2f"
}

Balance

Your workspace's wallet totals. Read only.

GET/v1/balance

Read your balance

Your workspace's wallet totals, the same figures the Boxmile web app's billing summary shows. availableCents is what label purchases can spend from: the balance less what is reserved and, while it is on, the carrier-adjustment reserve; it can be negative. reservedCents is the total held for purchases and disputes in flight. No hold by order, payment method or funding reference is shown. Test keys can read the balance.

Key scope balance.read

Responses

  • 200

    The balance.

  • 401

    The key is missing, malformed, unknown, revoked or expired.

    authentication.required

  • 403

    The key lacks this operation's scope, or its workspace is not active.

    access.denied

  • 429

    The bucket is empty. Wait Retry-After seconds.

    api.rate_limited

  • 503

    The wallet could not be read in time. Try again later.

    service.unavailable

GET /v1/balance
curl https://api.boxmile.com/v1/balance \
  -H "Authorization: Bearer $BOXMILE_API_KEY"
const response = await fetch('https://api.boxmile.com/v1/balance', {
  headers: {
    Authorization: `Bearer ${process.env.BOXMILE_API_KEY}`,
  },
});
const data = await response.json();
console.log(response.status, data);
import os

import requests

response = requests.get(
    "https://api.boxmile.com/v1/balance",
    headers={"Authorization": f"Bearer {os.environ['BOXMILE_API_KEY']}"},
    timeout=30,
)
print(response.status_code, response.json())
Response 200
{
  "object": "balance",
  "availableCents": 12345,
  "reservedCents": 300,
  "currency": "usd"
}
401authentication.required
Response 401
{
  "type": "about:blank",
  "title": "Sign in to continue.",
  "status": 401,
  "code": "authentication.required",
  "params": {},
  "locale": "en",
  "instance": "urn:boxmile:request:0192f7a0-5c1e-7d3a-9b2e-4f6a8c0d1e2f"
}
403access.denied
Response 403
{
  "type": "about:blank",
  "title": "You do not have permission to perform this action.",
  "status": 403,
  "code": "access.denied",
  "params": {},
  "locale": "en",
  "instance": "urn:boxmile:request:0192f7a0-5c1e-7d3a-9b2e-4f6a8c0d1e2f"
}
429api.rate_limited
Response 429
{
  "type": "about:blank",
  "title": "Too many requests. Wait the number of seconds in the Retry-After header, then try again.",
  "status": 429,
  "code": "api.rate_limited",
  "params": {},
  "locale": "en",
  "instance": "urn:boxmile:request:0192f7a0-5c1e-7d3a-9b2e-4f6a8c0d1e2f"
}
503service.unavailable
Response 503
{
  "type": "about:blank",
  "title": "This feature is not available yet. Try again later.",
  "status": 503,
  "code": "service.unavailable",
  "params": {},
  "locale": "en",
  "instance": "urn:boxmile:request:0192f7a0-5c1e-7d3a-9b2e-4f6a8c0d1e2f"
}