johnpaulmedina / laravel-usps
USPS Addresses API v3 (OAuth2) for Laravel
Requires
- php: ^8.1
- illuminate/cache: ^10.0 || ^11.0 || ^12.0 || ^13.0
- illuminate/http: ^10.0 || ^11.0 || ^12.0 || ^13.0
- illuminate/support: ^10.0 || ^11.0 || ^12.0 || ^13.0
Requires (Dev)
- guzzlehttp/guzzle: ^7.0
- guzzlehttp/psr7: ^2.0
- orchestra/testbench: ^8.0 || ^9.0 || ^10.0 || ^11.0
- phpunit/phpunit: ^10.0 || ^11.0
- dev-main
- v13.4.1
- v13.4.0
- v13.3.3
- v13.3.2
- v13.3.1
- v13.3.0
- v13.2.3
- v13.2.2
- v13.2.1
- v13.2
- v13.1
- v13.0
- dev-usps-api-sync/2026-04-09
- dev-usps-api-sync/2026-04-08
- dev-usps-api-sync/2026-04-07
- dev-usps-api-sync/2026-04-06
- dev-usps-api-sync/2026-04-05
- dev-usps-api-sync/2026-04-04
- dev-usps-api-sync/2026-04-03
- dev-usps-api-sync/2026-04-02
- dev-usps-api-sync/2026-04-01
- dev-usps-api-sync/2026-03-31
- dev-dependabot/github_actions/actions/checkout-6
- dev-usps-api-sync/2026-03-30
- dev-usps-api-sync/2026-03-29
- dev-usps-api-sync/2026-03-28
- dev-usps-api-sync/2026-03-27
- dev-usps-api-sync/2026-03-26
- dev-usps-api-sync/2026-03-25
- dev-copilot/security-audit-repository
This package is auto-updated.
Last update: 2026-04-09 06:36:07 UTC
README
Full Laravel package for the USPS API v3 (OAuth2). Covers all 20 USPS API domains: addresses, tracking, labels, international labels, domestic prices, international prices, service standards, locations, carrier pickup, containers, payments, campaigns, package campaigns, adjustments, disputes, appointments, shipping options, and SCAN forms.
Note: The legacy USPS Web Tools XML API was retired in January 2026. This package uses the new OAuth2-based REST/JSON API at
apis.usps.com.
Reference: Official USPS API examples and OpenAPI specs at github.com/USPS/api-examples.
Requirements
- PHP 8.1+
- Laravel 10, 11, 12, or 13
- USPS API credentials from developer.usps.com
Installation
composer require johnpaulmedina/laravel-usps
The service provider and facade are auto-discovered.
Configuration
Publish the config file:
php artisan vendor:publish --tag=usps-config
This creates config/usps.php:
return [ 'client_id' => env('USPS_CLIENT_ID', ''), 'client_secret' => env('USPS_CLIENT_SECRET', ''), ];
Add your credentials to .env:
USPS_CLIENT_ID=your-client-id
USPS_CLIENT_SECRET=your-client-secret
Artisan Commands
# Validate an address php artisan usps:validate "1600 Pennsylvania Ave NW" --state=DC --zip=20500 # Track packages php artisan usps:track 9400111899223456789012 # ZIP code lookup (city/state) php artisan usps:zip 33101 # Calculate shipping rates php artisan usps:price 20500 33101 16 --mail-class=PRIORITY_MAIL # Delivery estimates php artisan usps:standards 20500 33101 # Find USPS locations php artisan usps:locations 33101 --type=post-office --radius=5
All commands output formatted tables to the console.
Usage
All API domains are accessible via the Usps facade. Address methods are called directly; all other domains are accessed through fluent accessor methods that return typed API clients.
Addresses
use Usps; // Validate an address $result = Usps::validate([ 'Address' => '1600 Pennsylvania Ave NW', 'City' => 'Washington', 'State' => 'DC', 'Zip' => '20500', ]);
Response:
{
"address": {
"Address2": "1600 PENNSYLVANIA AVE NW",
"Address1": "",
"City": "WASHINGTON",
"State": "DC",
"Zip5": "20500",
"Zip4": "0005"
},
"additionalInfo": {
"deliveryPoint": "00",
"carrierRoute": "C000",
"DPVConfirmation": "Y",
"DPVCMRA": "N",
"business": "Y",
"centralDeliveryPoint": "",
"vacant": "N"
}
}
DPV (Delivery Point Validation) confirms whether a specific address is a real USPS deliverable location — down to the apartment/suite level.
Code Meaning YFull match — both street and unit confirmed DPrimary address confirmed, apartment/suite missing SPrimary confirmed, apartment/suite provided but doesn't match NNot a valid delivery point
When apartment is missing (e.g., multi-unit building):
{
"address": {
"Address2": "100 S BISCAYNE BLVD",
"Address1": "",
"City": "MIAMI",
"State": "FL",
"Zip5": "33131",
"Zip4": "2011"
},
"corrections": [
{
"code": "32",
"text": "Default address: The address you entered was found but more information is needed (such as an apartment, suite, or box number) to match to a specific address."
}
],
"additionalInfo": {
"deliveryPoint": "99",
"carrierRoute": "C038",
"DPVConfirmation": "D",
"DPVCMRA": "N",
"business": "N",
"centralDeliveryPoint": "",
"vacant": "N"
}
}
Correction codes:
32= apartment/suite needed,22= multiple addresses found.
// Full address lookup (raw v3 response) $result = Usps::addressLookup([ 'streetAddress' => '1600 Pennsylvania Ave NW', 'city' => 'Washington', 'state' => 'DC', ]);
Response:
{
"firm": "",
"address": {
"streetAddress": "1600 PENNSYLVANIA AVE NW",
"streetAddressAbbreviation": "1600 PENNSYLVANIA AVE NW",
"secondaryAddress": "",
"cityAbbreviation": "WASHINGTON",
"city": "WASHINGTON",
"state": "DC",
"ZIPCode": "20500",
"ZIPPlus4": "0005",
"urbanization": ""
},
"additionalInfo": { ... },
"corrections": [],
"matches": [{ "code": "31", "text": "Single Response - exact match" }]
}
// City/State lookup by ZIP $result = Usps::cityStateLookup('20500');
Response:
{
"city": "WASHINGTON",
"state": "DC",
"ZIPCode": "20500"
}
// ZIP Code lookup by address $result = Usps::zipCodeLookup([ 'streetAddress' => '1600 Pennsylvania Ave NW', 'city' => 'Washington', 'state' => 'DC', ]);
Response:
{
"firm": "",
"address": {
"streetAddress": "1600 PENNSYLVANIA AVE NW",
"city": "WASHINGTON",
"state": "DC",
"ZIPCode": "20500",
"ZIPPlus4": "0005"
}
}
Note: State names are auto-converted to abbreviations (
Florida→FL). ZIP codes with dashes are auto-split (20500-0005→ ZIP5:20500, ZIP4:0005).
USPS Address API Documentation: developer.usps.com/api/81 — full response field definitions, DPV codes, correction codes, and error handling.
Tracking
// Track packages (supports up to 35 at once) $result = Usps::tracking()->track([ ['trackingNumber' => '9400111899223456789012'], ]); // Register for email notifications Usps::tracking()->registerNotifications('9400111899223456789012', [ 'uniqueTrackingID' => 'abc-123', 'notifyEventTypes' => ['ALL_UPDATES'], 'recipients' => [['email' => 'user@example.com']], ]); // Request proof of delivery Usps::tracking()->proofOfDelivery('9400111899223456789012', [ 'uniqueTrackingID' => 'abc-123', 'recipients' => [['email' => 'user@example.com', 'firstName' => 'John', 'lastName' => 'Doe']], ]);
Domestic Labels
// Create a shipping label (requires payment authorization token) $label = Usps::labels()->createLabel($labelData, $paymentToken); $label = Usps::labels()->createLabel($labelData, $paymentToken, 'idempotency-key'); // Create a return label $label = Usps::labels()->createReturnLabel($labelData, $paymentToken); // Cancel/refund a label Usps::labels()->cancelLabel('9205500000000000000000'); // Edit label attributes Usps::labels()->editLabel('9205500000000000000000', $patchData); // First-Class indicia Usps::labels()->createIndicia($indiciaData, $paymentToken); // Intelligent Mail Barcode Usps::labels()->createImb($imbData, $paymentToken); Usps::labels()->cancelImb('00000000000000000000'); // Label branding Usps::labels()->uploadBranding($svgData); Usps::labels()->listBranding(limit: 10, offset: 0); Usps::labels()->getBranding('image-uuid'); Usps::labels()->deleteBranding('image-uuid'); Usps::labels()->renameBranding('image-uuid', ['name' => 'new-name']); // Reprint a label Usps::labels()->reprintLabel('9205500000000000000000', $reprintData, $paymentToken);
International Labels
$label = Usps::internationalLabels()->createLabel($labelData, $paymentToken); Usps::internationalLabels()->reprintLabel('CX123456789US', $reprintData, $paymentToken); Usps::internationalLabels()->cancelLabel('CX123456789US'); Usps::internationalLabels()->createIndicia($indiciaData, $paymentToken);
Domestic Prices
// Base rate search $rate = Usps::domesticPrices()->baseRateSearch([ 'originZIPCode' => '20500', 'destinationZIPCode' => '33101', 'weight' => 2.5, 'mailClass' => 'PRIORITY_MAIL', ]); // Extra service rate search $rate = Usps::domesticPrices()->extraServiceRateSearch($rateIngredients); // List eligible products $list = Usps::domesticPrices()->baseRateListSearch($rateIngredients); // Total rates (base + extra services) $total = Usps::domesticPrices()->totalRateSearch($rateIngredients); // First-Class letter rates $letter = Usps::domesticPrices()->letterRateSearch($rateIngredients);
International Prices
$rate = Usps::internationalPrices()->baseRateSearch($rateIngredients); $rate = Usps::internationalPrices()->extraServiceRateSearch($rateIngredients); $list = Usps::internationalPrices()->baseRateListSearch($rateIngredients); $total = Usps::internationalPrices()->totalRateSearch($rateIngredients); $letter = Usps::internationalPrices()->letterRateSearch($rateIngredients);
Service Standards
// Delivery estimates (with dates and acceptance locations) $estimates = Usps::serviceStandards()->getEstimates('20500', '33101', [ 'mailClass' => 'PRIORITY_MAIL', ]); // Service standards (average delivery days) $standards = Usps::serviceStandards()->getStandards('20500', '33101');
Service Standards Directory
// Get valid 5-digit ZIP codes $zips = Usps::serviceStandardsDirectory()->getValidZip5Codes(); // Get directory report (paginated) $report = Usps::serviceStandardsDirectory()->getReport([ 'originZIPCode' => '230', 'destinationZIPCode' => '330', 'responseFormat' => '3D_BASE', 'mailClass' => 'PERIODICALS', 'limit' => 10000, ]);
Service Standards Files
// List available files $files = Usps::serviceStandardsFiles()->listFiles(); // Get a signed download URL $download = Usps::serviceStandardsFiles()->generateSignedUrl('Combined_Service_Standard_Directory_MKT');
International Service Standards
$standard = Usps::internationalServiceStandards()->getServiceStandard('CA', 'PRIORITY_MAIL_INTERNATIONAL'); // Returns: { countryCode, mailClass, serviceStandardMessage }
Locations
// Dropoff locations for destination entry parcels $locations = Usps::locations()->getDropoffLocations('33101', [ 'mailClass' => 'PARCEL_SELECT', ]); // Post office locations $offices = Usps::locations()->getPostOfficeLocations([ 'ZIPCode' => '33101', 'radius' => 10, ]); // Parcel locker locations $lockers = Usps::locations()->getParcelLockerLocations(['ZIPCode' => '33101']);
Carrier Pickup
// Check pickup eligibility $eligible = Usps::carrierPickup()->checkEligibility('123 Main St', [ 'ZIPCode' => '33101', 'state' => 'FL', ]); // Schedule a pickup $pickup = Usps::carrierPickup()->schedulePickup([ 'pickupDate' => '2026-04-01', 'pickupAddress' => [...], 'packages' => [['packageType' => 'PRIORITY_MAIL', 'packageCount' => 2]], 'estimatedWeight' => 5.0, 'pickupLocation' => ['packageLocation' => 'FRONT_DOOR'], ]); // Get, update, cancel $details = Usps::carrierPickup()->getPickup('WTC12345'); Usps::carrierPickup()->updatePickup('WTC12345', $data, $etag); Usps::carrierPickup()->cancelPickup('WTC12345', $etag);
Containers
// Create a container label $container = Usps::containers()->createContainer($containerData); // Add/remove packages Usps::containers()->addPackages('CNT-001', ['trackingNumbers' => [...]]); Usps::containers()->removePackage('CNT-001', '9400111899223456789012'); Usps::containers()->removeAllPackages('CNT-001'); // Close container and generate manifest Usps::containers()->createManifest(['containers' => ['CNT-001']]);
Payments
// Create payment authorization token (required for labels) $auth = Usps::payments()->createPaymentAuthorization([ 'roles' => [ ['roleName' => 'PAYER', 'CRID' => '12345678', 'accountType' => 'EPS', 'accountNumber' => '1234'], ['roleName' => 'LABEL_OWNER', 'CRID' => '12345678', 'MID' => '123456', 'manifestMID' => '123456'], ], ]); $token = $auth['paymentAuthorizationToken']; // Check account balance $account = Usps::payments()->getPaymentAccount('12345678', 'EPS', ['amount' => 50.00]);
Informed Delivery Campaigns (Mail)
$campaign = Usps::campaigns()->createCampaign($campaignData); $list = Usps::campaigns()->searchCampaigns(['status' => 'ACTIVE']); $detail = Usps::campaigns()->getCampaign('CAM-001'); Usps::campaigns()->updateCampaign('CAM-001', $data); Usps::campaigns()->cancelCampaign('CAM-001'); Usps::campaigns()->addImbs('CAM-001', ['imbs' => [...]]); // Callback key management $keys = Usps::campaigns()->getCallbackKeys('12345'); $summary = Usps::campaigns()->getCallbackSummary('12345', 'key-abc'); $details = Usps::campaigns()->getCallbackDetails('12345', 'key-abc');
Informed Delivery Package Campaigns
$campaign = Usps::packageCampaigns()->createCampaign($campaignData); $list = Usps::packageCampaigns()->searchCampaigns(); $detail = Usps::packageCampaigns()->getCampaign('PKG-001'); Usps::packageCampaigns()->updateCampaign('PKG-001', $data); Usps::packageCampaigns()->cancelCampaign('PKG-001'); Usps::packageCampaigns()->addTrackingNumbers('PKG-001', ['trackingNumbers' => [...]]);
Adjustments
$adjustments = Usps::adjustments()->getAdjustments('12345678', '920011234561234567890', 'CENSUS'); $adjustments = Usps::adjustments()->getAdjustments('12345678', '920011234561234567890', 'DUPLICATES', [ 'destinationZIPCode' => '33101', ]);
Disputes
$dispute = Usps::disputes()->createDispute([ 'EPSTransactionID' => 'TXN-001', 'trackingID' => '920011234561234567890', 'CRID' => '12345678', 'reason' => 'INCORRECT_ASSESSED_WEIGHT', 'description' => 'Weight was measured incorrectly.', 'name' => 'John Doe', 'disputeCount' => '1', ]);
Appointments (FAST)
// Check availability $slots = Usps::appointments()->getAvailability(['facilityId' => 'FAC-001']); // Create, update, cancel $appt = Usps::appointments()->createAppointment($appointmentData); Usps::appointments()->updateAppointment($updateData); Usps::appointments()->cancelAppointment(['appointmentId' => 'APT-001']);
Shipping Options
// Combined pricing + service standards + available options in one call $options = Usps::shippingOptions()->search([ 'originZIPCode' => '20500', 'destinationZIPCode' => '33101', 'weight' => 2.5, 'length' => 12, 'width' => 8, 'height' => 4, 'mailClass' => 'PRIORITY_MAIL', ]);
SCAN Forms
// Label shipment — link tracking numbers to a single EFN $form = Usps::scanForms()->createLabelShipment([ 'trackingNumbers' => ['9400111899223456789012', '9400111899223456789013'], ]); // MID shipment $form = Usps::scanForms()->createMidShipment([ 'MID' => '123456', 'trackingNumbers' => ['9400111899223456789012'], ]); // Manifest MID shipment $form = Usps::scanForms()->createManifestMidShipment([ 'manifestMID' => '654321', 'MID' => '123456', ]);
API Domains
| Domain | Facade Accessor | Endpoints |
|---|---|---|
| Addresses | Direct methods | 3 |
| Tracking | tracking() |
3 |
| Domestic Labels | labels() |
12 |
| International Labels | internationalLabels() |
4 |
| Domestic Prices | domesticPrices() |
5 |
| International Prices | internationalPrices() |
5 |
| Service Standards | serviceStandards() |
2 |
| Service Standards Directory | serviceStandardsDirectory() |
2 |
| Service Standards Files | serviceStandardsFiles() |
2 |
| International Service Standards | internationalServiceStandards() |
1 |
| Locations | locations() |
3 |
| Carrier Pickup | carrierPickup() |
5 |
| Containers | containers() |
5 |
| Payments | payments() |
2 |
| Campaigns | campaigns() |
9 |
| Package Campaigns | packageCampaigns() |
6 |
| Adjustments | adjustments() |
1 |
| Disputes | disputes() |
1 |
| Appointments | appointments() |
4 |
| Shipping Options | shippingOptions() |
1 |
| SCAN Forms | scanForms() |
3 |
Authentication
The package automatically handles OAuth2 client credentials flow. Access tokens are cached per-scope for ~50 minutes to minimize token requests. Each API domain requests only the scopes it needs.
License
MIT