{
  "openapi": "3.1.0",
  "info": {
    "title": "ShipmentTrackingAPI",
    "version": "1.0.0",
    "summary": "A free tracking API for USPS, UPS, FedEx and DHL.",
    "description": "A free HTTP API that tracks USPS, UPS, FedEx and DHL packages. One endpoint answers all four carriers with the same nine statuses.\n\nLimits: 1,000 lookups a month and 30 requests a minute per account, and 1,000 lookups a month per IP address. One request is one lookup, however many tracking numbers it carries. USPS takes 35 at once; UPS, FedEx and DHL take 1.\n\nA request counts as one lookup when at least one number in it comes back with tracking data or with TRACKING_NOT_FOUND. Requests rejected with a 400, 429 or 503 are not counted.\n\nA lookup is not instant: set your HTTP client timeout to 60 seconds. A 503 was not counted and can be sent again.\n\nShipmentTrackingAPI is not affiliated with, endorsed by or sponsored by USPS, UPS, FedEx or DHL, and they do not provide or approve this data.",
    "termsOfService": "https://shipmenttrackingapi.com/terms",
    "contact": {
      "name": "ShipmentTrackingAPI",
      "url": "https://shipmenttrackingapi.com/docs",
      "email": "help@shipmenttrackingapi.com"
    }
  },
  "externalDocs": {
    "description": "API documentation",
    "url": "https://shipmenttrackingapi.com/docs"
  },
  "servers": [
    {
      "url": "https://api.shipmenttrackingapi.com"
    }
  ],
  "tags": [
    {
      "name": "Tracking"
    },
    {
      "name": "Carriers"
    }
  ],
  "paths": {
    "/v1/track": {
      "post": {
        "operationId": "trackPackages",
        "tags": [
          "Tracking"
        ],
        "summary": "Look up tracking numbers for one carrier",
        "description": "Send one carrier and an array of tracking numbers. `results` has one entry for each number, in the order you sent them.\n\nAn entry is either tracking data or an error for that one number. The HTTP status is 200 either way.\n\nRules: one carrier per request, no more numbers than that carrier takes, no number twice, and every number 6 to 40 letters and digits. USPS takes 35 at once; UPS, FedEx and DHL take 1.",
        "security": [
          {
            "apiKey": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/TrackRequest"
              },
              "example": {
                "carrier": "ups",
                "trackingNumbers": [
                  "YOUR_TRACKING_NUMBER"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "One result for each tracking number, in the order sent. A number without an answer has an `error` in its entry.",
            "headers": {
              "X-RateLimit-Limit-Minute": {
                "description": "Requests your account can send in one minute.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining-Minute": {
                "description": "Requests left in the current minute.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Limit-Month": {
                "description": "Lookups your account has for the month.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining-Month": {
                "description": "Lookups left this month.",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TrackResponse"
                },
                "example": {
                  "success": true,
                  "data": {
                    "carrier": "ups",
                    "results": [
                      {
                        "trackingNumber": "YOUR_TRACKING_NUMBER",
                        "status": "delivered",
                        "events": [
                          {
                            "timestamp": "2026-11-05T13:36:00-06:00",
                            "status": "delivered",
                            "description": "DELIVERED",
                            "location": "Chicago, IL, US"
                          }
                        ]
                      }
                    ]
                  }
                }
              }
            }
          },
          "400": {
            "description": "The request is not valid. Nothing was looked up or counted.\n- `INVALID_CARRIER`: The carrier is not one of usps, ups, fedex, dhl.\n- `INVALID_TRACKING_NUMBER`: A number is malformed, repeated, or not valid for that carrier. trackingNumbers is always an array.\n- `TOO_MANY_TRACKING_NUMBERS`: More numbers than the carrier takes in one request: 35 for USPS, 1 for the others.",
            "headers": {
              "X-RateLimit-Limit-Minute": {
                "description": "Requests your account can send in one minute.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining-Minute": {
                "description": "Requests left in the current minute.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Limit-Month": {
                "description": "Lookups your account has for the month.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining-Month": {
                "description": "Lookups left this month.",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "INVALID_CARRIER": {
                    "value": {
                      "success": false,
                      "error": {
                        "code": "INVALID_CARRIER",
                        "message": "carrier must be one of: usps, ups, fedex, dhl"
                      }
                    }
                  },
                  "INVALID_TRACKING_NUMBER": {
                    "value": {
                      "success": false,
                      "error": {
                        "code": "INVALID_TRACKING_NUMBER",
                        "message": "trackingNumbers[0] must be 6-40 alphanumeric characters"
                      }
                    }
                  },
                  "TOO_MANY_TRACKING_NUMBERS": {
                    "value": {
                      "success": false,
                      "error": {
                        "code": "TOO_MANY_TRACKING_NUMBERS",
                        "message": "UPS accepts 1 tracking number per request (2 sent)"
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "The API key is missing or not known.\n- `AUTH_MISSING_API_KEY`: There is no Authorization header.\n- `AUTH_INVALID_API_KEY`: The key does not exist.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "AUTH_MISSING_API_KEY": {
                    "value": {
                      "success": false,
                      "error": {
                        "code": "AUTH_MISSING_API_KEY",
                        "message": "Send your API key as: Authorization: Bearer <api key>"
                      }
                    }
                  },
                  "AUTH_INVALID_API_KEY": {
                    "value": {
                      "success": false,
                      "error": {
                        "code": "AUTH_INVALID_API_KEY",
                        "message": "Invalid API key"
                      }
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "The API key cannot be used.\n- `KEY_INACTIVE`: The key was turned off.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "KEY_INACTIVE": {
                    "value": {
                      "success": false,
                      "error": {
                        "code": "KEY_INACTIVE",
                        "message": "This API key has been deactivated"
                      }
                    }
                  }
                }
              }
            }
          },
          "429": {
            "description": "A limit was reached. Nothing was looked up or counted.\n- `RATE_LIMIT_EXCEEDED`: More than 30 requests in a minute. Retry-After says when to come back.\n- `MONTHLY_QUOTA_EXCEEDED`: The account has used its 1,000 lookups for the month.\n- `IP_QUOTA_EXCEEDED`: The IP address has used its 1,000 lookups for the month, across every key.",
            "headers": {
              "X-RateLimit-Limit-Minute": {
                "description": "Requests your account can send in one minute.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining-Minute": {
                "description": "Requests left in the current minute.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Limit-Month": {
                "description": "Lookups your account has for the month.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining-Month": {
                "description": "Lookups left this month.",
                "schema": {
                  "type": "integer"
                }
              },
              "Retry-After": {
                "description": "Only with RATE_LIMIT_EXCEEDED: the seconds until the next minute starts.",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "RATE_LIMIT_EXCEEDED": {
                    "value": {
                      "success": false,
                      "error": {
                        "code": "RATE_LIMIT_EXCEEDED",
                        "message": "Rate limit of 30 requests per minute exceeded"
                      }
                    }
                  },
                  "MONTHLY_QUOTA_EXCEEDED": {
                    "value": {
                      "success": false,
                      "error": {
                        "code": "MONTHLY_QUOTA_EXCEEDED",
                        "message": "Monthly quota of 1000 lookups exceeded"
                      }
                    }
                  },
                  "IP_QUOTA_EXCEEDED": {
                    "value": {
                      "success": false,
                      "error": {
                        "code": "IP_QUOTA_EXCEEDED",
                        "message": "This IP address has exceeded 1000 lookups this month and is blocked until the 1st of next month (UTC). Lookups from the same IP address are counted together across all API keys. If you share an outbound IP with other users (serverless platforms, shared NAT), route requests through a dedicated static IP."
                      }
                    }
                  }
                }
              }
            }
          },
          "500": {
            "description": "An unexpected error.\n- `INTERNAL_ERROR`: Something unexpected on our side. Try again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "INTERNAL_ERROR": {
                    "value": {
                      "success": false,
                      "error": {
                        "code": "INTERNAL_ERROR",
                        "message": "Internal server error"
                      }
                    }
                  }
                }
              }
            }
          },
          "503": {
            "description": "The lookup could not be completed. It was not counted and can be sent again.\n- `SERVICE_UNAVAILABLE`: Something on our side. Try again.",
            "headers": {
              "X-RateLimit-Limit-Minute": {
                "description": "Requests your account can send in one minute.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining-Minute": {
                "description": "Requests left in the current minute.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Limit-Month": {
                "description": "Lookups your account has for the month.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining-Month": {
                "description": "Lookups left this month.",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "SERVICE_UNAVAILABLE": {
                    "value": {
                      "success": false,
                      "error": {
                        "code": "SERVICE_UNAVAILABLE",
                        "message": "Tracking is temporarily unavailable. Please try again later."
                      }
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/v1/carriers": {
      "get": {
        "operationId": "listCarriers",
        "tags": [
          "Carriers"
        ],
        "summary": "List the carriers",
        "description": "Returns each carrier and how many tracking numbers it takes in one request. Needs no key and is not counted.",
        "security": [],
        "responses": {
          "200": {
            "description": "The carriers.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CarriersResponse"
                },
                "example": {
                  "success": true,
                  "data": {
                    "carriers": [
                      {
                        "id": "usps",
                        "name": "USPS",
                        "website": "https://www.usps.com",
                        "maxTrackingNumbers": 35
                      },
                      {
                        "id": "ups",
                        "name": "UPS",
                        "website": "https://www.ups.com",
                        "maxTrackingNumbers": 1
                      },
                      {
                        "id": "fedex",
                        "name": "FedEx",
                        "website": "https://www.fedex.com",
                        "maxTrackingNumbers": 1
                      },
                      {
                        "id": "dhl",
                        "name": "DHL",
                        "website": "https://www.dhl.com",
                        "maxTrackingNumbers": 1
                      }
                    ]
                  }
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "apiKey": {
        "type": "http",
        "scheme": "bearer",
        "description": "Your API key, sent as `Authorization: Bearer <key>`. Keys start with `sta_` and are free: sign in with Google at https://console.shipmenttrackingapi.com."
      }
    },
    "schemas": {
      "CarrierId": {
        "type": "string",
        "enum": [
          "usps",
          "ups",
          "fedex",
          "dhl"
        ],
        "description": "The carrier, in lowercase."
      },
      "TrackingStatus": {
        "type": "string",
        "enum": [
          "pending",
          "in_transit",
          "out_for_delivery",
          "delivered",
          "failed",
          "returned",
          "cancelled",
          "lost",
          "unknown"
        ],
        "description": "- `pending`: A label exists, but the carrier does not have the package yet.\n- `in_transit`: Moving through the carrier’s network. Also a missed delivery that can still be picked up or redelivered.\n- `out_for_delivery`: On the vehicle for the final delivery.\n- `delivered`: Handed over at the destination.\n- `failed`: The delivery could not be completed.\n- `returned`: Going back to the sender, or already back.\n- `cancelled`: The shipment was cancelled before delivery.\n- `lost`: The carrier reports the package missing.\n- `unknown`: The carrier said something the table does not cover yet."
      },
      "TrackRequest": {
        "type": "object",
        "required": [
          "carrier",
          "trackingNumbers"
        ],
        "properties": {
          "carrier": {
            "$ref": "#/components/schemas/CarrierId"
          },
          "trackingNumbers": {
            "type": "array",
            "description": "An array even for one number. No number twice. USPS takes 35 at once; UPS, FedEx and DHL take 1.",
            "minItems": 1,
            "maxItems": 35,
            "uniqueItems": true,
            "items": {
              "type": "string",
              "pattern": "^[A-Za-z0-9]{6,40}$",
              "description": "6 to 40 letters and digits, without spaces."
            }
          }
        }
      },
      "TrackResponse": {
        "type": "object",
        "required": [
          "success",
          "data"
        ],
        "properties": {
          "success": {
            "const": true
          },
          "data": {
            "type": "object",
            "required": [
              "carrier",
              "results"
            ],
            "properties": {
              "carrier": {
                "$ref": "#/components/schemas/CarrierId"
              },
              "results": {
                "type": "array",
                "description": "One entry for each number, in the order you sent them.",
                "items": {
                  "oneOf": [
                    {
                      "$ref": "#/components/schemas/TrackingResult"
                    },
                    {
                      "$ref": "#/components/schemas/TrackingFailure"
                    }
                  ]
                }
              }
            }
          }
        }
      },
      "TrackingResult": {
        "type": "object",
        "description": "Tracking data for one number.",
        "required": [
          "trackingNumber",
          "status",
          "events"
        ],
        "properties": {
          "trackingNumber": {
            "type": "string",
            "description": "The number you sent, in uppercase."
          },
          "status": {
            "$ref": "#/components/schemas/TrackingStatus",
            "description": "The package’s latest state."
          },
          "events": {
            "type": "array",
            "description": "Every scan, oldest first.",
            "items": {
              "$ref": "#/components/schemas/TrackingEvent"
            }
          },
          "estimatedDelivery": {
            "type": "string",
            "description": "ISO 8601. Present when the carrier gives one."
          },
          "lastUpdated": {
            "type": "string",
            "description": "ISO 8601. When this data was read from the carrier."
          }
        }
      },
      "TrackingEvent": {
        "type": "object",
        "required": [
          "timestamp",
          "status",
          "description"
        ],
        "properties": {
          "timestamp": {
            "type": "string",
            "description": "ISO 8601. USPS times are in UTC; the other carriers keep the local offset."
          },
          "timeUnknown": {
            "type": "boolean",
            "description": "Present and true when the carrier gave a date without a time. The time in `timestamp` is then a placeholder."
          },
          "status": {
            "$ref": "#/components/schemas/TrackingStatus",
            "description": "The status for that scan."
          },
          "description": {
            "type": "string",
            "description": "The carrier’s own words."
          },
          "location": {
            "type": "string",
            "description": "Where it was scanned, when the carrier says."
          }
        }
      },
      "TrackingFailure": {
        "type": "object",
        "description": "One number that has no answer. The other numbers in the request are not affected.",
        "required": [
          "trackingNumber",
          "error"
        ],
        "properties": {
          "trackingNumber": {
            "type": "string"
          },
          "error": {
            "type": "object",
            "required": [
              "code",
              "message"
            ],
            "properties": {
              "code": {
                "type": "string",
                "enum": [
                  "TRACKING_NOT_FOUND",
                  "INVALID_TRACKING_NUMBER",
                  "CARRIER_ERROR"
                ],
                "description": "- `TRACKING_NOT_FOUND`: The carrier has no record of that number. Makes the request count.\n- `INVALID_TRACKING_NUMBER`: Not a valid number for that carrier. Does not count.\n- `CARRIER_ERROR`: The carrier did not answer for that number. Send it again. Does not count."
              },
              "message": {
                "type": "string"
              }
            }
          }
        }
      },
      "Carrier": {
        "type": "object",
        "required": [
          "id",
          "name",
          "website",
          "maxTrackingNumbers"
        ],
        "properties": {
          "id": {
            "$ref": "#/components/schemas/CarrierId"
          },
          "name": {
            "type": "string"
          },
          "website": {
            "type": "string",
            "format": "uri"
          },
          "maxTrackingNumbers": {
            "type": "integer",
            "minimum": 1,
            "description": "How many tracking numbers this carrier takes in one request."
          }
        }
      },
      "CarriersResponse": {
        "type": "object",
        "required": [
          "success",
          "data"
        ],
        "properties": {
          "success": {
            "const": true
          },
          "data": {
            "type": "object",
            "required": [
              "carriers"
            ],
            "properties": {
              "carriers": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/Carrier"
                }
              }
            }
          }
        }
      },
      "Error": {
        "type": "object",
        "description": "The whole request failed. Switch on `code`; the wording of `message` can change.",
        "required": [
          "success",
          "error"
        ],
        "properties": {
          "success": {
            "const": false
          },
          "error": {
            "type": "object",
            "required": [
              "code",
              "message"
            ],
            "properties": {
              "code": {
                "type": "string",
                "enum": [
                  "INVALID_CARRIER",
                  "INVALID_TRACKING_NUMBER",
                  "TOO_MANY_TRACKING_NUMBERS",
                  "AUTH_MISSING_API_KEY",
                  "AUTH_INVALID_API_KEY",
                  "KEY_INACTIVE",
                  "NOT_FOUND",
                  "RATE_LIMIT_EXCEEDED",
                  "MONTHLY_QUOTA_EXCEEDED",
                  "IP_QUOTA_EXCEEDED",
                  "INTERNAL_ERROR",
                  "SERVICE_UNAVAILABLE"
                ]
              },
              "message": {
                "type": "string"
              }
            }
          }
        }
      }
    }
  }
}
