From d7ca041d22bd4cd211471bc2e0f093e079de34e6 Mon Sep 17 00:00:00 2001 From: Daniel K Date: Tue, 23 Jun 2026 01:33:23 -0400 Subject: [PATCH 1/6] fix(server): critical ops + security fixes for 2026.1.32 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - providers: guard migration 0093 against cascade data-loss (#1116) — depend on manager/0079 so carrier FK columns are dropped before the legacy carrier delete. - manager: make migration 0078 production-safe — chunked iterator + bulk_update, idempotent (#1123). - events: batch periodic_data_archiving deletes to avoid first-run OOM (#1125). - settings: import workers before apm so huey binds REDIS_HOST under OTEL (#1124). - settings: scope MD5 PASSWORD_HASHERS to the test runner only (#1096, @mgradalska). - core: clean up async DB connections to stop the tracing connection leak (#1119/#1120 phase 1, @ChrisNolan) + phase-2 PRD. --- ...RACING_PERSISTENCE_PHASE2_HUEY_REFACTOR.md | 206 ++++++++++++++++++ apps/api/karrio/server/settings/__init__.py | 9 +- apps/api/karrio/server/settings/base.py | 8 +- apps/api/karrio/server/test_runner.py | 13 ++ .../core/tests/test_async_db_cleanup.py | 28 +++ modules/core/karrio/server/core/utils.py | 26 ++- .../0093_migrate_system_carriers_data.py | 14 +- .../events/task_definitions/base/archiving.py | 122 ++++++----- .../server/events/tests/test_archiving.py | 47 ++++ .../0078_populate_carrier_snapshots.py | 94 +++++--- 10 files changed, 471 insertions(+), 96 deletions(-) create mode 100644 PRDs/TRACING_PERSISTENCE_PHASE2_HUEY_REFACTOR.md create mode 100644 apps/api/karrio/server/test_runner.py create mode 100644 modules/core/karrio/server/core/tests/test_async_db_cleanup.py create mode 100644 modules/events/karrio/server/events/tests/test_archiving.py diff --git a/PRDs/TRACING_PERSISTENCE_PHASE2_HUEY_REFACTOR.md b/PRDs/TRACING_PERSISTENCE_PHASE2_HUEY_REFACTOR.md new file mode 100644 index 0000000000..86667046db --- /dev/null +++ b/PRDs/TRACING_PERSISTENCE_PHASE2_HUEY_REFACTOR.md @@ -0,0 +1,206 @@ +# Tracing Persistence Phase 2: Move API Thread Async to Huey DB Task + +| Field | Value | +| --------- | ---------------- | +| Project | Karrio | +| Version | 1.0 | +| Date | 2026-06-09 | +| Status | Planning | +| Owner | Server/Core Team | +| Type | Refactoring | +| Reference | AGENTS.md | + +--- + +## Executive Summary + +Phase 1 mitigates idle PostgreSQL session buildup by cleaning DB connections in API-side async threads. +Phase 2 removes this risk class entirely by migrating tracing persistence from request-local thread execution to the existing Huey `db_task` pattern already used for server background ORM work. + +### Key Decisions + +1. Persist tracing records via Huey `db_task` instead of `@utils.async_wrapper`. +2. Keep existing tracing record schema and payload shape unchanged. +3. Keep deduplication by `request_log_id` in worker task logic. +4. Roll out behind existing `PERSIST_SDK_TRACING` flag with no API contract changes. + +### Scope + +| In Scope | Out of Scope | +| --------------------------------------------- | -------------------------------------- | +| Move persistence execution path to Huey task | Changes to tracing record model fields | +| Keep existing dedupe and org-link behavior | Tracing UI/query redesign | +| Add task-focused tests and integration checks | Broad logging/telemetry redesign | + +--- + +## Problem Statement + +### Current State + +Tracing persistence for API requests is triggered in middleware and currently uses an async wrapper backed by a thread executor in server core utils. + +Relevant files: + +- `modules/core/karrio/server/core/middleware.py` +- `modules/core/karrio/server/tracing/utils.py` +- `modules/core/karrio/server/core/utils.py` + +### Why Change + +Even with Phase 1 cleanup, API request handling still depends on ad hoc thread async for ORM persistence. +Karrio already has a standard and safer background ORM pattern via Huey `db_task` wrappers in events tasks. + +### Desired State + +API middleware enqueues a Huey tracing persistence task. Worker-side execution handles ORM writes and org-linking under known task lifecycle semantics. + +--- + +## Existing Code Analysis + +| Component | Location | Reuse Strategy | +| ----------------------- | ----------------------------------------------------------------------- | ---------------------------------------------------- | +| Tracing save entrypoint | `modules/core/karrio/server/tracing/utils.py` | Split into enqueue + worker task body | +| Request middleware hook | `modules/core/karrio/server/core/middleware.py` | Keep call site stable, change implementation beneath | +| Huey task pattern | `modules/events/karrio/server/events/task_definitions/base/__init__.py` | Mirror `@db_task` + tenant-aware style | +| Worker settings | `apps/api/karrio/server/settings/workers.py` | Reuse existing queue execution model | + +--- + +## Architecture Overview + +```text +Before (current) + +HTTP Request + | + v +SessionContext middleware + | + v +save_tracing_records() + | + v +ThreadPoolExecutor (API process) + | + v +ORM bulk_create("tracing-record") + + +After (phase 2) + +HTTP Request + | + v +SessionContext middleware + | + v +enqueue_tracing_records_task(...) + | + v +Huey queue + | + v +Huey worker db_task + | + v +ORM bulk_create("tracing-record") +``` + +## Sequence + +```text +Client -> API: request +API -> Middleware: complete response +Middleware -> Tracing Utils: enqueue(payload) +Tracing Utils -> Huey: task.delay(payload) +Huey Worker -> DB: dedupe check + bulk_create +Huey Worker -> DB: bulk_link_org (if org) +``` + +--- + +## Technical Design + +1. Create a dedicated tracing persistence task function in server-side tasks module. +2. Move ORM write logic from nested async closure into task body. +3. Keep payload minimal and serializable: + - actor_id + - org_id + - schema + - tracer context values (`request_id`, `request_log_id`, `object_id`) + - flattened tracing records list (key, timestamp, record, connection metadata) +4. Preserve behavior: + - skip when `PERSIST_SDK_TRACING` is false + - skip when no records or no actor + - preserve request_log_id dedupe check + - preserve org linking +5. Keep middleware call shape unchanged to minimize blast radius. + +### Compatibility Notes + +- No API schema changes. +- No migration required. +- Existing tracing readers remain unchanged. + +--- + +## Implementation Plan + +| Step | Change | Files | +| ---- | -------------------------------------------------------- | ---------------------------------------------------------------------------------------- | +| 1 | Add Huey db_task for tracing persistence | `modules/events/karrio/server/events/task_definitions/base/*.py` | +| 2 | Refactor tracing util to enqueue task payload | `modules/core/karrio/server/tracing/utils.py` | +| 3 | Keep middleware integration stable | `modules/core/karrio/server/core/middleware.py` | +| 4 | Add tests for enqueue + worker persistence behavior | `modules/core/karrio/server/core/tests/*`, `modules/events/karrio/server/events/tests/*` | +| 5 | Validate under load and compare pg_stat_activity profile | ops verification | + +--- + +## Testing Strategy + +1. Unit tests + - enqueue is called once per request context with expected payload + - worker task no-ops on empty records or missing actor + - worker task dedupe by `request_log_id` +2. Integration tests + - middleware path still results in saved tracing records + - org links are created correctly when org exists +3. Non-functional validation + - run load test with tracing enabled + - compare idle `karrio.api` DB sessions before/after + +--- + +## Risks and Mitigations + +| Risk | Impact | Mitigation | +| ---------------------------------- | --------------------------------- | ----------------------------------------------------------------------------------- | +| Task payload missing context field | Missing metadata in trace records | Contract test asserting payload keys | +| Queue lag delays trace visibility | Delayed debugging data | Document eventual consistency; keep synchronous fallback toggle for troubleshooting | +| Duplicate writes in retries | Data noise | Keep request_log_id dedupe guard in task | + +--- + +## Migration and Rollback + +### Migration + +- Deploy code with task path enabled. +- Keep `PERSIST_SDK_TRACING` configurable for staged rollout. + +### Rollback + +- Revert to previous tracing utils implementation. +- Disable tracing persistence (`PERSIST_SDK_TRACING=False`) if immediate operational relief is needed. + +--- + +## Definition of Done + +- [ ] Tracing persistence no longer uses API-side thread async path. +- [ ] Tracing writes run through Huey `db_task` worker path. +- [ ] Existing tracing metadata and dedupe behavior preserved. +- [ ] Tests added and passing for enqueue and persistence logic. +- [ ] Load validation shows stable/expected idle DB session count. diff --git a/apps/api/karrio/server/settings/__init__.py b/apps/api/karrio/server/settings/__init__.py index d7158d3dac..475f01cdad 100644 --- a/apps/api/karrio/server/settings/__init__.py +++ b/apps/api/karrio/server/settings/__init__.py @@ -3,9 +3,16 @@ import importlib.util from karrio.server.settings.base import * + +# IMPORTANT: workers must be imported before apm. apm's OTEL block does +# `from huey.contrib.djhuey import HUEY`, and djhuey reads `settings.HUEY` once +# at import time — falling back to a localhost RedisHuey (and caching it for the +# process) if it is not yet defined. workers defines settings.HUEY, so importing +# it first ensures djhuey binds to the configured broker even when OTEL_ENABLED +# forces the early djhuey import (GH #1124). +from karrio.server.settings.workers import * from karrio.server.settings.apm import * from karrio.server.settings.cache import * -from karrio.server.settings.workers import * from karrio.server.settings.debug import * from karrio.server.settings.email import * from karrio.server.settings.constance import * diff --git a/apps/api/karrio/server/settings/base.py b/apps/api/karrio/server/settings/base.py index ce8784434f..84377e0e05 100644 --- a/apps/api/karrio/server/settings/base.py +++ b/apps/api/karrio/server/settings/base.py @@ -374,13 +374,7 @@ } } -# Speed up test suite: use fast MD5 hasher instead of bcrypt/PBKDF2 -# This only applies when running `manage.py test` — production is unaffected -import sys as _sys -if "test" in _sys.argv or "karrio" in _sys.argv[0]: - PASSWORD_HASHERS = [ - "django.contrib.auth.hashers.MD5PasswordHasher", - ] +TEST_RUNNER = "karrio.server.test_runner.KarrioTestRunner" if config("DATABASE_URL", default=None): db_from_env = dj_database_url.config( diff --git a/apps/api/karrio/server/test_runner.py b/apps/api/karrio/server/test_runner.py new file mode 100644 index 0000000000..bdf371f0d8 --- /dev/null +++ b/apps/api/karrio/server/test_runner.py @@ -0,0 +1,13 @@ +from django.conf import settings +from django.test.runner import DiscoverRunner + + +class KarrioTestRunner(DiscoverRunner): + """Custom Django test runner that overrides settings for the test suite.""" + + def setup_test_environment(self, **kwargs): + super().setup_test_environment(**kwargs) + # Speed up tests with a fast hasher (PBKDF2 is intentionally slow). + settings.PASSWORD_HASHERS = [ + "django.contrib.auth.hashers.MD5PasswordHasher", + ] diff --git a/modules/core/karrio/server/core/tests/test_async_db_cleanup.py b/modules/core/karrio/server/core/tests/test_async_db_cleanup.py new file mode 100644 index 0000000000..f4466de2e1 --- /dev/null +++ b/modules/core/karrio/server/core/tests/test_async_db_cleanup.py @@ -0,0 +1,28 @@ +from unittest import TestCase +from unittest.mock import patch + +from karrio.server.core import utils + + +class TestAsyncDbCleanup(TestCase): + def test_run_async_cleans_up_db_connections_on_success(self): + with patch("karrio.server.core.utils.close_old_connections") as close_old, patch.object( + utils.connections, "close_all" + ) as close_all: + result = utils.run_async(lambda: "ok").result(timeout=2) + + self.assertEqual(result, "ok") + close_old.assert_called_once_with() + close_all.assert_called_once_with() + + def test_run_async_cleans_up_db_connections_on_error(self): + with patch("karrio.server.core.utils.close_old_connections") as close_old, patch.object( + utils.connections, "close_all" + ) as close_all: + future = utils.run_async(lambda: (_ for _ in ()).throw(ValueError("boom"))) + + with self.assertRaises(ValueError): + future.result(timeout=2) + + close_old.assert_called_once_with() + close_all.assert_called_once_with() \ No newline at end of file diff --git a/modules/core/karrio/server/core/utils.py b/modules/core/karrio/server/core/utils.py index 0acc443c9b..ebb7e2f152 100644 --- a/modules/core/karrio/server/core/utils.py +++ b/modules/core/karrio/server/core/utils.py @@ -2,11 +2,13 @@ import typing import inspect import functools +import atexit from concurrent import futures from datetime import timedelta, datetime, timezone from typing import TypeVar, Union, Callable, Any, List, Optional from django.conf import settings +from django.db import close_old_connections, connections from django.utils.translation import gettext_lazy as _ import django_email_verification.confirm as confirm import rest_framework_simplejwt.tokens as jwt @@ -20,6 +22,17 @@ T = TypeVar("T") +# Reuse a bounded executor for server-side fire-and-forget operations to avoid +# creating one thread pool per request and leaking thread-local DB connections. +_ASYNC_EXECUTOR = futures.ThreadPoolExecutor(max_workers=4) + + +@atexit.register +def _shutdown_async_executor(): + # Registered with atexit: invoked automatically when the process exits. + _ASYNC_EXECUTOR.shutdown(wait=False, cancel_futures=True) + + def identity(value: Union[Any, Callable]) -> Any: """ :param value: function or value desired to be wrapped @@ -144,7 +157,18 @@ def run_async(callable: Callable[[], Any]) -> futures.Future: of a callable in a non-blocking thread and return a handle for a future response. """ - return futures.ThreadPoolExecutor(max_workers=1).submit(callable) + + def _wrapped_call(): + # Ensure this worker thread starts from a clean DB connection state. + close_old_connections() + + try: + return callable() + finally: + # Close thread-local DB connections opened during background ORM work. + connections.close_all() + + return _ASYNC_EXECUTOR.submit(_wrapped_call) def error_wrapper(func): diff --git a/modules/core/karrio/server/providers/migrations/0093_migrate_system_carriers_data.py b/modules/core/karrio/server/providers/migrations/0093_migrate_system_carriers_data.py index f2354b67d7..ab8583114f 100644 --- a/modules/core/karrio/server/providers/migrations/0093_migrate_system_carriers_data.py +++ b/modules/core/karrio/server/providers/migrations/0093_migrate_system_carriers_data.py @@ -182,7 +182,10 @@ def migrate_system_carriers(apps, schema_editor): except Exception: pass # active_users may not exist in schema yet - # 4. Delete the original Carrier record (orgs refs already cleaned up at start) + # 4. Delete the original Carrier record (orgs refs already cleaned up at start). + # Safe against cascade data-loss: this migration depends on + # manager.0079, so the manager carrier FK columns are already removed + # and this delete cannot cascade into tracking/shipment/pickup history. carrier.delete() @@ -239,6 +242,15 @@ class Migration(migrations.Migration): dependencies = [ ("providers", "0092_add_system_brokered_connection_models_update_carrier"), + # Guard against cascade data-loss (GH discussion #1116): + # Deleting the legacy is_system Carrier rows (see migrate_system_carriers) + # cascades through the manager FKs (tracking_carrier, pickup_carrier, + # selected_rate_carrier, manifest_carrier, upload_carrier) — all CASCADE, + # most NOT NULL — and silently destroys tracking/shipment/pickup history. + # manager.0079 removes those FK columns AFTER snapshots are copied in + # manager.0078, so depending on it here guarantees the columns no longer + # exist when carrier.delete() runs and the delete cannot cascade. + ("manager", "0079_remove_carrier_fk_fields"), # Depend on orgs migration if orgs module is installed # This ensures BrokeredConnectionLink exists for org-scoped migrations ] diff --git a/modules/events/karrio/server/events/task_definitions/base/archiving.py b/modules/events/karrio/server/events/task_definitions/base/archiving.py index fb3cf7690a..2557460cfa 100644 --- a/modules/events/karrio/server/events/task_definitions/base/archiving.py +++ b/modules/events/karrio/server/events/task_definitions/base/archiving.py @@ -10,6 +10,36 @@ import karrio.server.tracing.models as tracing import karrio.server.manager.models as manager +# Delete in bounded batches so a large first-run backlog cannot load every id +# into memory and OOM the worker in a single unbounded transaction (GH #1125). +BATCH_SIZE = 1000 + + +def _batched_delete(queryset, pk_field="id"): + """Delete a queryset in fixed-size batches, one transaction per batch. + + Returns the total number of deleted rows. Each batch resolves a bounded + page of primary keys and deletes only those, so memory stays flat + regardless of how much backlog has accumulated. + """ + ordered = queryset.order_by("pk") + total_deleted = 0 + + while True: + batch_ids = list(ordered.values_list(pk_field, flat=True)[:BATCH_SIZE]) + if not batch_ids: + break + + deleted_count = ordered.filter(**{f"{pk_field}__in": batch_ids}).delete()[0] + total_deleted += deleted_count + + # Stop if a batch deleted nothing to avoid an infinite loop on rows that + # cannot be removed (e.g. protected relations). + if not deleted_count: + break + + return total_deleted + def run_data_archiving(*args, **kwargs): now = timezone.now() @@ -38,7 +68,7 @@ def run_data_archiving(*args, **kwargs): deleted_records=tracing_deleted, ) - events_deleted = utils.failsafe(lambda: event_data.delete()[0]) or 0 + events_deleted = utils.failsafe(lambda: _batched_delete(event_data)) or 0 if events_deleted: logger.info( "Archiving events backlog", @@ -46,7 +76,7 @@ def run_data_archiving(*args, **kwargs): deleted_records=events_deleted, ) - api_logs_deleted = utils.failsafe(lambda: api_log_data.delete()[0]) or 0 + api_logs_deleted = utils.failsafe(lambda: _batched_delete(api_log_data)) or 0 if api_logs_deleted: logger.info( "Archiving API request logs backlog", @@ -127,74 +157,62 @@ def _bulk_delete_tracing_data(tracing_queryset): return total_deleted -def _bulk_delete_tracking_data(tracking_queryset): - """Bulk delete tracking data to avoid N+1 queries with organization links.""" - queryset = tracking_queryset.order_by("pk") +def _batched_delete_with_links(queryset, link_model): + """Delete a queryset in batches, clearing matching org links per batch. - try: - from karrio.server.orgs.models import TrackingLink + Mirrors ``_batched_delete`` but first removes the organization link rows + (``item_id`` based) for each batch to avoid CASCADE N+1 queries, then + deletes the batch itself. Bounded memory regardless of backlog (GH #1125). + """ + ordered = queryset.order_by("pk") + total_deleted = 0 - # Get the tracking record IDs that will be deleted - tracking_ids = list(queryset.values_list("id", flat=True)) + while True: + batch_ids = list(ordered.values_list("id", flat=True)[:BATCH_SIZE]) + if not batch_ids: + break - if tracking_ids: - # Bulk delete TrackingLink entries first to avoid CASCADE N+1 queries - TrackingLink.objects.filter(item_id__in=tracking_ids).delete() + if link_model is not None: + link_model.objects.filter(item_id__in=batch_ids).delete() - # Now delete the tracking records themselves - deleted = queryset.delete()[0] + deleted_count = ordered.filter(id__in=batch_ids).delete()[0] + total_deleted += deleted_count - except ImportError: - # Organizations module not installed, just delete normally - deleted = queryset.delete()[0] + if not deleted_count: + break - return deleted + return total_deleted -def _bulk_delete_shipment_data(shipment_queryset): - """Bulk delete shipment data to avoid N+1 queries with organization links.""" - queryset = shipment_queryset.order_by("pk") - +def _bulk_delete_tracking_data(tracking_queryset): + """Batch-delete tracking data, clearing organization links per batch.""" try: - from karrio.server.orgs.models import ShipmentLink - - # Get the shipment record IDs that will be deleted - shipment_ids = list(queryset.values_list("id", flat=True)) + from karrio.server.orgs.models import TrackingLink as link_model + except ImportError: + # Organizations module not installed. + link_model = None - if shipment_ids: - # Bulk delete ShipmentLink entries first to avoid CASCADE N+1 queries - ShipmentLink.objects.filter(item_id__in=shipment_ids).delete() + return _batched_delete_with_links(tracking_queryset, link_model) - # Now delete the shipment records themselves - deleted = queryset.delete()[0] +def _bulk_delete_shipment_data(shipment_queryset): + """Batch-delete shipment data, clearing organization links per batch.""" + try: + from karrio.server.orgs.models import ShipmentLink as link_model except ImportError: - # Organizations module not installed, just delete normally - deleted = queryset.delete()[0] + # Organizations module not installed. + link_model = None - return deleted + return _batched_delete_with_links(shipment_queryset, link_model) def _bulk_delete_order_data(order_queryset): - """Bulk delete order data to avoid N+1 queries with organization links.""" - queryset = order_queryset.order_by("pk") - + """Batch-delete order data, clearing organization links per batch.""" try: - from karrio.server.orgs.models import OrderLink - - # Get the order record IDs that will be deleted - order_ids = list(queryset.values_list("id", flat=True)) - - if order_ids: - # Bulk delete OrderLink entries first to avoid CASCADE N+1 queries - OrderLink.objects.filter(item_id__in=order_ids).delete() - - # Now delete the order records themselves - deleted = queryset.delete()[0] - + from karrio.server.orgs.models import OrderLink as link_model except ImportError: - # Organizations module not installed, just delete normally - deleted = queryset.delete()[0] + # Organizations module not installed. + link_model = None - return deleted + return _batched_delete_with_links(order_queryset, link_model) diff --git a/modules/events/karrio/server/events/tests/test_archiving.py b/modules/events/karrio/server/events/tests/test_archiving.py new file mode 100644 index 0000000000..2acd1fb0f8 --- /dev/null +++ b/modules/events/karrio/server/events/tests/test_archiving.py @@ -0,0 +1,47 @@ +from unittest import mock + +from karrio.server.graph.tests.base import GraphTestCase +import karrio.server.events.models as events +from karrio.server.events.task_definitions.base import archiving + + +class TestArchivingBatching(GraphTestCase): + def test_batched_delete_spans_multiple_batches(self): + """_batched_delete must loop until the queryset is drained (GH #1125). + + A small BATCH_SIZE forces several iterations so a regression that only + deletes the first page (or loops forever) is caught. + """ + events.Event.objects.bulk_create( + [ + events.Event(type="test_event", test_mode=False, created_by=self.user) + for _ in range(5) + ] + ) + seeded = events.Event.objects.count() + self.assertGreaterEqual(seeded, 5) + + with mock.patch.object(archiving, "BATCH_SIZE", 2): + deleted = archiving._batched_delete(events.Event.objects.all()) + + self.assertEqual(deleted, seeded) + self.assertEqual(events.Event.objects.count(), 0) + + def test_batched_delete_with_links_drains_queryset(self): + """The link-aware variant must also drain across batches (GH #1125).""" + events.Event.objects.bulk_create( + [ + events.Event(type="test_event", test_mode=False, created_by=self.user) + for _ in range(3) + ] + ) + seeded = events.Event.objects.count() + + with mock.patch.object(archiving, "BATCH_SIZE", 1): + # link_model=None exercises the no-orgs path without extra fixtures. + deleted = archiving._batched_delete_with_links( + events.Event.objects.all(), None + ) + + self.assertEqual(deleted, seeded) + self.assertEqual(events.Event.objects.count(), 0) diff --git a/modules/manager/karrio/server/manager/migrations/0078_populate_carrier_snapshots.py b/modules/manager/karrio/server/manager/migrations/0078_populate_carrier_snapshots.py index 9c0328378b..ee484830b1 100644 --- a/modules/manager/karrio/server/manager/migrations/0078_populate_carrier_snapshots.py +++ b/modules/manager/karrio/server/manager/migrations/0078_populate_carrier_snapshots.py @@ -1,7 +1,17 @@ # Data migration: Populate carrier JSONField from FK relationships +import logging + from django.db import migrations +logger = logging.getLogger("karrio.server") + +# Stream rows in bounded chunks and write them back with bulk_update so the +# migration never loads an entire table into memory or issues one UPDATE per row +# (GH #1123). Each table is processed independently and is idempotent: rows whose +# snapshot is already populated are skipped, so re-running is a no-op. +BATCH_SIZE = 2000 + def get_carrier_name(carrier): """Get carrier name from carrier code.""" @@ -28,6 +38,42 @@ def create_carrier_snapshot(carrier): } +def _populate_from_fk(Model, fk_field, label): + """Copy a carrier FK snapshot into the ``carrier`` JSONField in bounded batches. + + Streams only rows that still need a snapshot (FK set, carrier empty) via a + server-side cursor, and writes them back with bulk_update in fixed-size + batches. Returns the number of rows updated. + """ + queryset = ( + Model.objects.filter(**{f"{fk_field}__isnull": False, "carrier__isnull": True}) + .select_related(fk_field) + .order_by("pk") + ) + + batch = [] + total = 0 + + def flush(): + nonlocal total + if batch: + Model.objects.bulk_update(batch, ["carrier"], batch_size=BATCH_SIZE) + total += len(batch) + logger.info(f"0078 populate {label}: {total} rows updated") + batch.clear() + + for instance in queryset.iterator(chunk_size=BATCH_SIZE): + carrier = getattr(instance, fk_field) + if carrier and not instance.carrier: + instance.carrier = create_carrier_snapshot(carrier) + batch.append(instance) + if len(batch) >= BATCH_SIZE: + flush() + + flush() + return total + + def populate_carrier_snapshots(apps, schema_editor): """ Populate carrier JSONField from FK relationships. @@ -38,42 +84,22 @@ def populate_carrier_snapshots(apps, schema_editor): - DocumentUploadRecord.upload_carrier -> DocumentUploadRecord.carrier - Manifest.manifest_carrier -> Manifest.carrier - Shipment.selected_rate_carrier -> Shipment.carrier (dedicated field, consistent with other models) + + Production-safe (GH #1123): each table is streamed in chunks and written + back with bulk_update — no per-row save, no full-table load — and is + idempotent so it can be re-run safely. """ - Pickup = apps.get_model("manager", "Pickup") - Tracking = apps.get_model("manager", "Tracking") - DocumentUploadRecord = apps.get_model("manager", "DocumentUploadRecord") - Manifest = apps.get_model("manager", "Manifest") - Shipment = apps.get_model("manager", "Shipment") + mappings = [ + ("Pickup", "pickup_carrier"), + ("Tracking", "tracking_carrier"), + ("DocumentUploadRecord", "upload_carrier"), + ("Manifest", "manifest_carrier"), + ("Shipment", "selected_rate_carrier"), + ] - # Populate Pickup.carrier from pickup_carrier FK - for pickup in Pickup.objects.select_related("pickup_carrier").all(): - if pickup.pickup_carrier and not pickup.carrier: - pickup.carrier = create_carrier_snapshot(pickup.pickup_carrier) - pickup.save(update_fields=["carrier"]) - - # Populate Tracking.carrier from tracking_carrier FK - for tracking in Tracking.objects.select_related("tracking_carrier").all(): - if tracking.tracking_carrier and not tracking.carrier: - tracking.carrier = create_carrier_snapshot(tracking.tracking_carrier) - tracking.save(update_fields=["carrier"]) - - # Populate DocumentUploadRecord.carrier from upload_carrier FK - for record in DocumentUploadRecord.objects.select_related("upload_carrier").all(): - if record.upload_carrier and not record.carrier: - record.carrier = create_carrier_snapshot(record.upload_carrier) - record.save(update_fields=["carrier"]) - - # Populate Manifest.carrier from manifest_carrier FK - for manifest in Manifest.objects.select_related("manifest_carrier").all(): - if manifest.manifest_carrier and not manifest.carrier: - manifest.carrier = create_carrier_snapshot(manifest.manifest_carrier) - manifest.save(update_fields=["carrier"]) - - # Populate Shipment.carrier from selected_rate_carrier FK (consistent with other models) - for shipment in Shipment.objects.select_related("selected_rate_carrier").all(): - if shipment.selected_rate_carrier and not shipment.carrier: - shipment.carrier = create_carrier_snapshot(shipment.selected_rate_carrier) - shipment.save(update_fields=["carrier"]) + for model_name, fk_field in mappings: + Model = apps.get_model("manager", model_name) + _populate_from_fk(Model, fk_field, model_name) def reverse_migration(apps, schema_editor): From e1754707ebcba368deb2be62c72eabbcd4d31723 Mon Sep 17 00:00:00 2001 From: Daniel K Date: Tue, 23 Jun 2026 01:33:23 -0400 Subject: [PATCH 2/6] fix(usps): correct v3 API hosts + vendor official OpenAPI specs - usps + usps_international: update server URLs to apis.usps.com / apis-tem.usps.com after USPS retired the legacy Web Tools / api-cat hosts (#1118, @zebradots). - vendor the official USPS Developer Portal v3 specs (captured 2026-06-23) for usps and usps_international, each with a provenance README. --- .../usps/karrio/providers/usps/utils.py | 2 +- modules/connectors/usps/vendor/README.md | 37 + modules/connectors/usps/vendor/addresses.yaml | 866 +++ .../usps/vendor/carrier-pickup.yaml | 908 +++ .../usps/vendor/domestic-labels.yaml | 6655 +++++++++++++++++ .../usps/vendor/domestic-prices.yaml | 116 +- modules/connectors/usps/vendor/locations.yaml | 1563 ++++ modules/connectors/usps/vendor/payments.yaml | 575 ++ .../connectors/usps/vendor/scan-forms.yaml | 609 ++ .../usps/vendor/shipping-options.yaml | 1322 ++++ modules/connectors/usps/vendor/tracking.yaml | 1145 +++ .../providers/usps_international/utils.py | 2 +- .../usps_international/vendor/README.md | 20 + .../vendor/international-labels.yaml | 2446 ++++++ .../vendor/international-prices.yaml | 1889 +++++ 15 files changed, 18100 insertions(+), 55 deletions(-) create mode 100644 modules/connectors/usps/vendor/README.md create mode 100644 modules/connectors/usps/vendor/addresses.yaml create mode 100644 modules/connectors/usps/vendor/carrier-pickup.yaml create mode 100644 modules/connectors/usps/vendor/domestic-labels.yaml create mode 100644 modules/connectors/usps/vendor/locations.yaml create mode 100644 modules/connectors/usps/vendor/payments.yaml create mode 100644 modules/connectors/usps/vendor/scan-forms.yaml create mode 100644 modules/connectors/usps/vendor/shipping-options.yaml create mode 100644 modules/connectors/usps/vendor/tracking.yaml create mode 100644 modules/connectors/usps_international/vendor/README.md create mode 100644 modules/connectors/usps_international/vendor/international-labels.yaml create mode 100644 modules/connectors/usps_international/vendor/international-prices.yaml diff --git a/modules/connectors/usps/karrio/providers/usps/utils.py b/modules/connectors/usps/karrio/providers/usps/utils.py index 2563372919..d15ecff240 100644 --- a/modules/connectors/usps/karrio/providers/usps/utils.py +++ b/modules/connectors/usps/karrio/providers/usps/utils.py @@ -29,7 +29,7 @@ def carrier_name(self): @property def server_url(self): - return "https://api-cat.usps.com" if self.test_mode else "https://apis.usps.com" + return "https://apis-tem.usps.com" if self.test_mode else "https://apis.usps.com" @property def tracking_url(self): diff --git a/modules/connectors/usps/vendor/README.md b/modules/connectors/usps/vendor/README.md new file mode 100644 index 0000000000..f3f90329f1 --- /dev/null +++ b/modules/connectors/usps/vendor/README.md @@ -0,0 +1,37 @@ +# USPS vendor specs + +Official USPS OpenAPI specifications, vendored for reference and schema generation. +Captured **2026-06-23** from the USPS Developer Portal (). + +All v3 APIs share the base hosts: + +- Production: `https://apis.usps.com` +- Test (TEM — Testing Environment for Mailers): `https://apis-tem.usps.com` + +| File | API | Version | Server base | Portal page | +| --- | --- | --- | --- | --- | +| `domestic-prices.yaml` | Domestic Prices | 3.4.30 | `/prices/v3` | [domesticpricesv3](https://developers.usps.com/domesticpricesv3) | +| `domestic-labels.yaml` | Labels | 3.9.13 | `/labels/v3` | [domesticlabelsv3](https://developers.usps.com/domesticlabelsv3) | +| `tracking.yaml` | Package Tracking & Notification | 3.2.6 | `/tracking/v3` | [trackingv3](https://developers.usps.com/trackingv3) | +| `addresses.yaml` | Addresses | 3.2.3 | `/addresses/v3` | [addressesv3](https://developers.usps.com/addressesv3) | +| `locations.yaml` | Locations | 3.5.6 | `/locations/v3` | [locationsv3](https://developers.usps.com/locationsv3) | +| `carrier-pickup.yaml` | Carrier Pickup | 3.1.8 | `/pickup/v3` | [carrierpickupv3](https://developers.usps.com/carrierpickupv3) | +| `payments.yaml` | Payments | 3.1.20 | `/payments/v3` | [paymentsv3](https://developers.usps.com/paymentsv3) | +| `shipping-options.yaml` | Shipping Options | 3.1.29 | `/shipments/v3` | [shippingoptionsv3](https://developers.usps.com/shippingoptionsv3) | +| `scan-forms.yaml` | SCAN Forms | 3.1.14 | `/scan-forms/v3` | [scanv3](https://developers.usps.com/scanv3) | + +OAuth 2.0 (`/oauth2/v3/token`) has no standalone spec — see the portal +[Getting Started](https://developers.usps.com/getting-started) guide. + +## Refreshing + +Each portal page embeds a Redoc `spec-url` pointing at the raw YAML under +`https://developers.usps.com/sites/default/files/apidoc_specs/_.yaml` +(the `_` suffix increments per revision). To refresh, open the portal page, +read its `spec-url`, and re-download: + +```bash +# example +curl -sS "https://developers.usps.com/domesticpricesv3" \ + | grep -oiE '/sites/default/files/apidoc_specs/[^" ]*\.yaml' | head -1 +``` diff --git a/modules/connectors/usps/vendor/addresses.yaml b/modules/connectors/usps/vendor/addresses.yaml new file mode 100644 index 0000000000..ecd74868ff --- /dev/null +++ b/modules/connectors/usps/vendor/addresses.yaml @@ -0,0 +1,866 @@ +openapi: 3.0.1 +info: + title: Addresses + description: | + Contact Us: [USPS API Support](https://emailus.usps.com/s/usps-APIs) | [Terms of Service](https://about.usps.com/termsofuse.htm) + + The Addresses API validates and corrects address information to improve package delivery service and pricing. This suite of APIs provides different utilities for addressing components. The ZIP Code™ lookup finds valid ZIP Code™(s) for a City and State. The City/State lookup provides the valid cities and states for a provided ZIP Code™. The Address Standardization API validates and standardizes USPS® domestic addresses, city and state names, and ZIP Code™ in accordance with USPS® addressing standards. The USPS® address standard includes the ZIP + 4®, signifying a USPS® delivery point, given a street address, a city and a state. + + version: 3.2.3 +servers: + - url: https://apis.usps.com/addresses/v3 + description: Production Environment Endpoint + - url: https://apis-tem.usps.com/addresses/v3 + description: Testing Environment Endpoint +paths: + /address: + get: + tags: + - Resources + summary: Returns the best standardized address for a given address. + description: |- + Standardizes street addresses including city and street abbreviations as well as providing missing information such as ZIP Code™ and ZIP + 4®. + + Must specify a street address, a state, and either a city or a ZIP Code™. + operationId: get-address + parameters: + - name: firm + in: query + description: "Firm/business corresponding to the address." + schema: + type: string + maxLength: 50 + minLength: 0 + - name: streetAddress + in: query + description: The number of a building along with the name of the road or street on which it is located. + required: true + schema: + type: string + - name: secondaryAddress + in: query + description: "The secondary unit designator, such as apartment(APT) or suite(STE) number, defining the exact location of the address within a building. For more information please see [Postal Explorer](https://pe.usps.com/text/pub28/28c2_003.htm)." + required: false + schema: + type: string + - name: city + in: query + description: This is the city name of the address. + required: false + schema: + type: string + - name: state + in: query + description: The two-character state code of the address. + required: true + schema: + maxLength: 2 + minLength: 2 + pattern: ^(AA|AE|AL|AK|AP|AS|AZ|AR|CA|CO|CT|DE|DC|FM|FL|GA|GU|HI|ID|IL|IN|IA|KS|KY|LA|ME|MH|MD|MA|MI|MN|MS|MO|MP|MT|NE|NV|NH|NJ|NM|NY|NC|ND|OH|OK|OR|PW|PA|PR|RI|SC|SD|TN|TX|UT|VT|VI|VA|WA|WV|WI|WY)$ + type: string + - name: urbanization + in: query + description: This is the urbanization code relevant only for Puerto Rico addresses. + required: false + schema: + type: string + - name: ZIPCode + in: query + description: This is the 5-digit ZIP code. + required: false + schema: + pattern: "^\\d{5}$" + type: string + - name: ZIPPlus4 + in: query + description: This is the 4-digit component of the ZIP+4 code. Using the correct ZIP+4 reduces the number of times your mail is handled and can decrease the chance of a misdelivery or error. + required: false + schema: + pattern: "^\\d{4}$" + type: string + responses: + "200": + description: Successful operation. + content: + application/json: + schema: + $ref: '#/components/schemas/AddressResponse' + application/xml: + schema: + $ref: '#/components/schemas/AddressResponse' + "400": + description: Bad Request. There is an error in the received request. + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorMessage' + application/xml: + schema: + $ref: '#/components/schemas/ErrorMessage' + "401": + description: Unauthorized request. + headers: + WWW-Authenticate: + $ref: '#/components/headers/WWWAuthenticate' + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorMessage' + application/xml: + schema: + $ref: '#/components/schemas/ErrorMessage' + "403": + description: Access is denied. + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorMessage' + application/xml: + schema: + $ref: '#/components/schemas/ErrorMessage' + "404": + description: Address Not Found. + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorMessage' + examples: + Address Not Found: + $ref: '#/components/examples/Address-Not-Found' + No Match: + $ref: '#/components/examples/No-Match' + Invalid State Code: + $ref: '#/components/examples/Invalid-State-Code' + Invalid City: + $ref: '#/components/examples/Invalid-City' + Unverifiable City and State: + $ref: '#/components/examples/Unverifiable-City-and-State' + Insufficient Address Data: + $ref: '#/components/examples/Insufficient-Adddress-Data' + Invalid Delivery Address: + $ref: '#/components/examples/Invalid-Delivery-Address' + Multiple Addresses Found: + $ref: '#/components/examples/Multiple-Addresses-Found' + application/xml: + schema: + $ref: '#/components/schemas/ErrorMessage' + "429": + description: Too Many Requests. Too many requests have been received from the client in a short amount of time. + headers: + Retry-After: + $ref: '#/components/headers/RetryAfter' + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorMessage' + application/xml: + schema: + $ref: '#/components/schemas/ErrorMessage' + "503": + description: Service is unavailable. + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorMessage' + application/xml: + schema: + $ref: '#/components/schemas/ErrorMessage' + default: + description: Other unanticipated errors that may occur. + content: {} + security: + - OAuth: + - addresses + /city-state: + get: + tags: + - Resources + summary: Returns the city and state for a given ZIP Code. + description: Returns the city and state corresponding to the given ZIP Code™. + operationId: get-city-state + parameters: + - name: ZIPCode + in: query + description: This is the 5-digit ZIP code. + required: true + schema: + pattern: "^\\d{5}$" + type: string + responses: + "200": + description: Successful operation. + content: + application/json: + schema: + $ref: '#/components/schemas/CityStateResponse' + application/xml: + schema: + $ref: '#/components/schemas/CityStateResponse' + "400": + description: A bad request was received. + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorMessage' + application/xml: + schema: + $ref: '#/components/schemas/ErrorMessage' + "401": + description: Unauthorized request. + headers: + WWW-Authenticate: + $ref: '#/components/headers/WWWAuthenticate' + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorMessage' + application/xml: + schema: + $ref: '#/components/schemas/ErrorMessage' + "403": + description: Access is denied. + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorMessage' + application/xml: + schema: + $ref: '#/components/schemas/ErrorMessage' + "429": + description: Too Many Requests. Too many requests have been received from the client in a short amount of time. + headers: + Retry-After: + $ref: '#/components/headers/RetryAfter' + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorMessage' + application/xml: + schema: + $ref: '#/components/schemas/ErrorMessage' + "503": + description: Service is unavailable. + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorMessage' + application/xml: + schema: + $ref: '#/components/schemas/ErrorMessage' + default: + description: Other unanticipated errors that may occur. + content: {} + security: + - OAuth: + - addresses + /zipcode: + get: + tags: + - Resources + summary: Returns the ZIP Code for a given address. + description: "Returns the ZIP Code™ and ZIP + 4® corresponding to the given address, city, and state (use USPS state abbreviations)." + operationId: get-ZIPCode + parameters: + - name: firm + in: query + description: "Firm/business corresponding to the address." + schema: + type: string + maxLength: 50 + minLength: 0 + - name: streetAddress + in: query + description: The number of a building along with the name of the road or street on which it is located. + required: true + schema: + type: string + - name: secondaryAddress + in: query + description: "The secondary unit designator, such as apartment(APT) or suite(STE) number, defining the exact location of the address within a building. For more information please see [Postal Explorer](https://pe.usps.com/text/pub28/28c2_003.htm)." + required: false + schema: + type: string + - name: city + in: query + description: This is the city name of the address. + required: true + schema: + type: string + - name: state + in: query + description: This is the two-character state code of the address. + required: true + schema: + maxLength: 2 + minLength: 2 + pattern: ^(AA|AE|AL|AK|AP|AS|AZ|AR|CA|CO|CT|DE|DC|FM|FL|GA|GU|HI|ID|IL|IN|IA|KS|KY|LA|ME|MH|MD|MA|MI|MN|MS|MO|MP|MT|NE|NV|NH|NJ|NM|NY|NC|ND|OH|OK|OR|PW|PA|PR|RI|SC|SD|TN|TX|UT|VT|VI|VA|WA|WV|WI|WY)$ + type: string + - name: ZIPCode + in: query + description: This is the 5-digit ZIP code. + required: false + schema: + pattern: "^\\d{5}$" + type: string + - name: ZIPPlus4 + in: query + description: This is the 4-digit component of the ZIP+4 code. Using the correct ZIP+4 reduces the number of times your mail is handled and can decrease the chance of a misdelivery or error. + required: false + schema: + pattern: "^\\d{4}$" + type: string + responses: + "200": + description: Successful operation. + content: + application/json: + schema: + $ref: '#/components/schemas/ZIPCodeResponse' + application/xml: + schema: + $ref: '#/components/schemas/ZIPCodeResponse' + "400": + description: There is an error in the received request. + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorMessage' + application/xml: + schema: + $ref: '#/components/schemas/ErrorMessage' + "401": + description: Unauthorized request. + headers: + WWW-Authenticate: + $ref: '#/components/headers/WWWAuthenticate' + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorMessage' + application/xml: + schema: + $ref: '#/components/schemas/ErrorMessage' + "403": + description: Access is denied. + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorMessage' + application/xml: + schema: + $ref: '#/components/schemas/ErrorMessage' + "429": + description: Too Many Requests. Too many requests have been received from the client in a short amount of time. + headers: + Retry-After: + $ref: '#/components/headers/RetryAfter' + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorMessage' + application/xml: + schema: + $ref: '#/components/schemas/ErrorMessage' + "503": + description: Service is unavailable. + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorMessage' + application/xml: + schema: + $ref: '#/components/schemas/ErrorMessage' + default: + description: Other unanticipated errors that may occur. + content: {} + security: + - OAuth: + - addresses +components: + schemas: + AddressResponse: + title: Address Response + type: object + properties: + firm: + maxLength: 50 + minLength: 0 + type: string + description: This is the firm/business name at the address. + address: + $ref: '#/components/schemas/DomesticAddress' + additionalInfo: + $ref: '#/components/schemas/AddressAdditionalInfo' + corrections: + $ref: '#/components/schemas/AddressCorrections' + matches: + $ref: '#/components/schemas/AddressMatches' + warnings: + type: array + xml: + wrapped: true + items: + type: string + xml: + name: warning + additionalProperties: false + description: "Standardizes street addresses including city and street abbreviations, and provides missing information such as ZIP Code™ and ZIP + 4®." + xml: + name: AddressValidateResponse + wrapped: true + CityAndState: + title: City And State + type: object + properties: + city: + maxLength: 28 + minLength: 1 + type: string + description: This is the city name of the address. + example: "Des Moines" + state: + maxLength: 2 + minLength: 2 + pattern: "\\w{2}" + type: string + description: This is two-character state code of the address. + example: IA + ZIPCode: + maxLength: 5 + minLength: 5 + type: string + description: This is the ZIP Code of the address. + example: "50314" + AddressCorrections: + title: Address Corrections + type: array + description: | + Codes that indicate how to improve the address input to get a better match. + + Code `32` will indicate "Default address: The address you entered was found but more information is needed (such as an apartment, suite, or box number." The recommended change would be to add additional information, such as an apartment, suite, or box number, to match to a specific address. + + Code `22` will indicate "Multiple addresses were found for the information you entered, and no default exists." The address could not be resolved as entered and more information would be needed to identify the address. + xml: + name: addressCorrections + wrapped: true + items: + type: object + properties: + code: + maxLength: 2 + minLength: 1 + pattern: "\\w{2}" + type: string + description: The code corresponding to the address correction. + xml: + name: code + text: + type: string + description: This is the description of the address correction. + xml: + name: text + additionalProperties: false + xml: + name: addressCorrection + AddressMatches: + title: Address Matches + type: array + description: | + Codes that indicate if an address is an exact match. + + Code `31` will be returned "Single Response - exact match" indicating that the address was correctly matched to a ZIP+4 record. + xml: + name: addressMatches + wrapped: true + items: + type: object + properties: + code: + maxLength: 2 + minLength: 1 + pattern: "\\w{2}" + type: string + xml: + name: code + text: + type: string + xml: + name: text + additionalProperties: false + xml: + name: addressMatch + AddressAdditionalInfo: + title: Address Additional Information + type: object + properties: + deliveryPoint: + type: string + description: |- + A specific set of digits between 00 and 99 is assigned to every address that is combined with the ZIP + 4® Code to provide a unique identifier for every delivery address. + + A street address does not necessarily represent a single delivery point because a street address such as one for an apartment building may have several delivery points. + carrierRoute: + maxLength: 5 + minLength: 0 + type: string + description: This is the carrier route code (values unspecified). + example: "C012" + DPVConfirmation: + type: string + description: | + The DPV Confirmation indicator identifies whether the address provided maps to a known USPS address record, whether the USPS delivers to the address or not. If the USPS does not deliver to the address, the USPS may deliver to a PO Box instead. `carrierRoute` values of `R777` and `R779`, for example, may require the shipper to ask the recipient where they receive their USPS mail, which may be different than their physical address. + + * `Y` - Address was DPV confirmed for both primary and (if present) secondary numbers. A value of `Y` does not necessarily imply that USPS delivers to that address. + * `D` - Address was DPV confirmed for the primary number only, and the secondary number information was missing. + * `S` - Address was DPV confirmed for the primary number only, and the secondary number information was present but not confirmed. + * `N` - Both primary and (if present) secondary number information failed to DPV confirm. + enum: + - "Y" + - D + - S + - "N" + DPVCMRA: + type: string + description: |- + Indicates if the location is a [Commercial Mail Receiving Agency (CMRA)](https://faq.usps.com/s/article/Commercial-Mail-Receiving-Agency-CMRA). + * `Y` - Address was found in the CMRA table. + * `N` - Address was not found in the CMRA table. + enum: + - "Y" + - "N" + business: + type: string + description: | + Indicates whether this is a business address. + * `Y` - The address is a business address. + * `N` - The address is not a business address. + enum: + - "Y" + - "N" + centralDeliveryPoint: + type: string + description: | + Central Delivery is for all business office buildings and/or industrial/professional parks. This may include call windows, horizontal locked mail receptacles, and cluster box units. + * `Y` - The address is a central delivery point. + * `N` - The address is not a central delivery point. + enum: + - "Y" + - "N" + vacant: + type: string + description: | + Indicates whether the location designated by the address is occupied. + * `Y` - The address is not occupied. + * `N` - The address is occupied. + enum: + - "Y" + - "N" + additionalProperties: false + description: Extra information about the request. + xml: + name: addressAdditionalInfo + CityStateResponse: + title: City and State Response + description: The validated ZIP Code™ for a given city and state. + xml: + name: CityStateLookupResponse + wrapped: true + allOf: + - $ref: '#/components/schemas/CityAndState' + ZIPCodeResponse: + title: ZIP Code™ Response + type: object + properties: + firm: + maxLength: 50 + minLength: 0 + type: string + description: This is the firm/business name at the address. + address: + $ref: '#/components/schemas/DomesticAddress' + additionalProperties: false + description: The address to validate the ZIP Code™ for. + xml: + name: ZipCodeLookupResponse + wrapped: true + ErrorMessage: + title: Error + type: object + properties: + apiVersion: + type: string + description: The version of the API that was used and that raised the error. + error: + type: object + properties: + code: + type: string + description: The error status code that has been returned in response to the request. + message: + type: string + description: A human-readable message describing the error. + errors: + type: array + items: + type: object + properties: + status: + type: string + description: The status code response returned to the client. + code: + type: string + description: An internal subordinate code used for error diagnosis. + title: + type: string + description: A human-readable title that identifies the error. + detail: + type: string + description: A human-readable description of the error that occurred. + source: + type: object + properties: + parameter: + type: string + description: The input in the request which caused an error. + example: + type: string + description: An example of a valid value for the input parameter. + additionalProperties: true + description: The element that is suspected of originating the error. Helps to pinpoint the problem. + additionalProperties: true + additionalProperties: true + description: The high-level error that has occurred as indicated by the status code. + additionalProperties: true + description: Standard error message response. + DomesticAddress: + title: Domestic Address + additionalProperties: true + description: Address fields for US locations + allOf: + - $ref: '#/components/schemas/Address' + - type: object + properties: + city: + maxLength: 28 + minLength: 1 + type: string + description: This is the city name of the address. + state: + $ref: '#/components/schemas/State' + ZIPCode: + pattern: "\\d{5}" + type: string + description: This is the 5-digit ZIP code. + ZIPPlus4: + pattern: "\\d{4}" + type: string + description: This is the 4-digit component of the ZIP+4 code. Using the correct ZIP+4 reduces the number of times your mail is handled and can decrease the chance of a misdelivery or error. + nullable: true + urbanization: + maxLength: 96 + type: string + description: "An area, sector, or residential development within a geographic area (typically used for addresses in Puerto Rico)." + additionalProperties: true + Address: + title: Address + type: object + properties: + streetAddress: + maxLength: 50 + minLength: 1 + type: string + description: The number of a building along with the name of the road or street on which it is located. + streetAddressAbbreviation: + maxLength: 50 + minLength: 0 + type: string + description: This is the abbreviation of the primary street address line for the address. + readOnly: true + secondaryAddress: + maxLength: 50 + type: string + description: "The secondary unit designator, such as apartment(APT) or suite(STE) number, defining the exact location of the address within a building. For more information please see [Postal Explorer](https://pe.usps.com/text/pub28/28c2_003.htm)." + cityAbbreviation: + type: string + description: This is the abbreviation of the city name for the address. + readOnly: true + additionalProperties: true + description: Address fields standard to all locations. + xml: + name: Address + State: + maxLength: 2 + minLength: 2 + pattern: ^(AA|AE|AL|AK|AP|AS|AZ|AR|CA|CO|CT|DE|DC|FM|FL|GA|GU|HI|ID|IL|IN|IA|KS|KY|LA|ME|MH|MD|MA|MI|MN|MS|MO|MP|MT|NE|NV|NH|NJ|NM|NY|NC|ND|OH|OK|OR|PW|PA|PR|RI|SC|SD|TN|TX|UT|VT|VI|VA|WA|WV|WI|WY)$ + type: string + description: The two-character state code. + parameters: + StreetAddress-Required: + name: streetAddress + in: query + description: The number of a building along with the name of the road or street on which it is located. + required: true + schema: + type: string + SecondaryAddress: + name: secondaryAddress + in: query + description: "The secondary unit designator, such as apartment(APT) or suite(STE) number, defining the exact location of the address within a building. For more information please see [Postal Explorer](https://pe.usps.com/text/pub28/28c2_003.htm)." + required: false + schema: + type: string + City: + name: city + in: query + description: This is the city name of the address. + required: false + schema: + type: string + State-Required: + name: state + in: query + description: This is two-character state code of the address. + required: true + schema: + maxLength: 2 + minLength: 2 + pattern: ^(AA|AE|AL|AK|AP|AS|AZ|AR|CA|CO|CT|DE|DC|FM|FL|GA|GU|HI|ID|IL|IN|IA|KS|KY|LA|ME|MH|MD|MA|MI|MN|MS|MO|MP|MT|NE|NV|NH|NJ|NM|NY|NC|ND|OH|OK|OR|PW|PA|PR|RI|SC|SD|TN|TX|UT|VT|VI|VA|WA|WV|WI|WY)$ + type: string + Urbanization: + name: urbanization + in: query + description: This is the urbanization code relevant only for Puerto Rico addresses. + required: false + schema: + type: string + ZIPCode: + name: ZIPCode + in: query + description: This is the 5-digit ZIP code. + required: false + schema: + pattern: "^\\d{5}$" + type: string + ZIPPlus4: + name: ZIPPlus4 + in: query + description: This is the 4-digit component of the ZIP+4 code. Using the correct ZIP+4 reduces the number of times your mail is handled and can decrease the chance of a misdelivery or error. + required: false + schema: + pattern: "^\\d{4}$" + type: string + ZIPCode-Required: + name: ZIPCode + in: query + description: This is the 5-digit ZIP code. + required: true + schema: + pattern: "^\\d{5}$" + type: string + City-Required: + name: city + in: query + description: This is the city name of the address. + required: true + schema: + type: string + examples: + Invalid-City: + summary: The city is missing or invalid. + description: The city is missing or invalid. + value: + apiVersion: v3 + error: + code: "404" + message: The city in the request is missing or invalid. + errors: [] + Invalid-State-Code: + summary: The two-letter state code is missing or invalid. + description: The two-letter state code is missing or invalid. + value: + apiVersion: v3 + error: + code: "404" + message: The state code in the request is missing or invalid. + errors: [] + Unverifiable-City-and-State: + summary: The city and state are missing or together unverifiable. + description: The city and state are missing or together unverifiable. + value: + apiVersion: v3 + error: + code: "404" + message: The city and state are missing or together unverifiable. + errors: [] + Insufficient-Adddress-Data: + summary: The address information in the request is insufficient to match. + description: The address information in the request is insufficient to match. + value: + apiVersion: v3 + error: + code: "404" + message: The address information in the request is insufficient to match. + errors: [] + Address-Not-Found: + summary: The address requested could not be found. + description: "There is no match for the specified address, try adding as much information as possible." + value: + apiVersion: v3 + error: + code: "404" + message: There is no match for the address requested. + errors: [] + No-Match: + summary: Could not find any matching address. + description: "There is no match for the specified address, try adding as much information as possible." + value: + apiVersion: v3 + error: + code: "404" + message: There is no match for the address requested. + errors: [] + Invalid-Delivery-Address: + summary: The address requested is an invalid delivery address. + description: The address requested is an invalid delivery address. + value: + apiVersion: v3 + error: + code: "404" + message: The address requested is an invalid delivery address. + errors: [] + Multiple-Addresses-Found: + summary: More than one address was found matching the requested address. + description: More than one address was found matching the requested address. + value: + apiVersion: v3 + error: + code: "404" + message: More than one address was found matching the requested address. + errors: [] + headers: + WWWAuthenticate: + description: Hint to the client application which security scheme to authorize a resource request. + required: false + schema: + type: string + example: "WWW-Authenticate: Bearer realm=\"https://api.usps.com\"" + RetryAfter: + description: Indicate to the client application a time after which they can retry a resource request. + required: false + schema: + type: string + example: "Retry-After: 30" + securitySchemes: + OAuth: + type: oauth2 + description: The specified APIs accept an access token formatted as a JSON Web Token. The relative path to the OAuth2 version 3 API which supplies this access token is provided below for reference. + flows: + clientCredentials: + tokenUrl: /oauth2/v3/token + scopes: + addresses: read-only access to all addresses endpoints + authorizationCode: + authorizationUrl: /oauth2/v3/authorize + tokenUrl: /oauth2/v3/token + scopes: + addresses: read-only access to all addresses endpoints diff --git a/modules/connectors/usps/vendor/carrier-pickup.yaml b/modules/connectors/usps/vendor/carrier-pickup.yaml new file mode 100644 index 0000000000..d844386dff --- /dev/null +++ b/modules/connectors/usps/vendor/carrier-pickup.yaml @@ -0,0 +1,908 @@ +openapi: 3.0.1 +info: + title: Carrier Pickup + description: | + Contact Us: [USPS API Support](https://emailus.usps.com/s/usps-APIs) | [Terms of Service](https://about.usps.com/termsofuse.htm) + + Carrier Pickup supports customers scheduling a carrier to pick up packages for free. Carrier Pickup is available for Priority Mail Express®, Priority Mail®, USPS Ground Advantage™, International delivery services and returns. + + You are able check carrier pickup service availability, and schedule, inquire, change, and cancel a carrier pickup. + + Use the eligibility API to verify that the USPS can pick up at your address. + + [What is Package Pickup?](https://faq.usps.com/s/article/What-is-Package-Pickup) + version: 3.1.8 +servers: +- url: https://apis.usps.com/pickup/v3 + description: Production Environment Endpoint +- url: https://apis-tem.usps.com/pickup/v3 + description: Testing Environment Endpoint +paths: + /carrier-pickup/eligibility: + get: + tags: + - Resources + summary: Check pick-up address eligibility. + description: |- + Check carrier pickup service availability at the specified address. + Either the city and state or the ZIP Code™ is required, in addition to the street address. + Successful responses include the USPS standardized address when this location is eligible for carrier pickup. + operationId: get-carrier-pickup-eligibility + parameters: + - name: streetAddress + in: query + description: The number of a building along with the name of the road or street on which it is located. + required: true + schema: + type: string + - name: secondaryAddress + in: query + description: "The secondary unit designator, such as apartment(APT) or suite(STE) number, defining the exact location of the address within a building. For more information please see [Postal Explorer](https://pe.usps.com/text/pub28/28c2_003.htm)." + required: false + schema: + type: string + - name: city + in: query + description: This is the city name of the address. + required: false + schema: + type: string + - name: state + in: query + description: This is the state name or two-character state code of the address. The request accepts either the full state name or a two-character state code. The response will always return the two-character state code. + required: false + schema: + maxLength: 100 + minLength: 2 + pattern: ^(AA|aa|AE|ae|AP|ap|AL|al|alabama|AK|ak|alaska|AS|as|((am(\.|erican)?[ \t]+)?samoa)|AZ|az|arizona|AR|ar|arkansas|CA|ca|california|CO|co|colorado|CT|ct|connecticut|DC|dc|district of columbia|(d\.[ \t]*c\.)|DE|de|delaware|FL|fl|florida|FM|fm|((federated[ \t]+states[ \t]+of[ \t]+)?micronesia)|GA|ga|georgia|GU|gu|guam|HI|hi|hawaii|IA|ia|iowa|ID|id|idaho|IL|il|illinois|IN|in|indiana|KS|ks|kansas|KY|ky|kentucky|LA|la|louisiana|MA|ma|massachusetts|MD|md|maryland|ME|me|maine|MH|mh|(marshall[ \t]+is(\.|lands)?)|MI|mi|michigan|MN|mn|minnesota|MO|mo|missouri|MP|mp|((northern[ \t]+)?mariana(s|[ \t]+is(\.|lands)?))|MS|ms|mississippi|MT|mt|montana|NC|nc|north carolina|(n\.[ \t]*c\.)|ND|nd|north dakota|(n\.[ \t]*d\.)|NE|ne|nebraska|NH|nh|new hampshire|(n\.[ \t]*h\.)|NJ|nj|new jersey|(n\.[ \t]*j\.)|NM|nm|new mexico|(n\.[ \t]*m\.)|NV|nv|nevada|NY|ny|new york|(n\.[ \t]*y\.)|OH|oh|ohio|OK|ok|oklahoma|OR|or|oregon|PA|pa|pennsylvania|PR|pr|(p(\.|uerto)?[ \t]*r(\.|ico)?)|PW|pw|palau|RI|ri|rhode island|(r\.[ \t]*i\.)|SC|sc|south carolina|(s\.[ \t]*c\.)|SD|sd|south dakota|(s\.[ \t]*d\.)|TN|tn|tennessee|TX|tx|texas|UT|ut|utah|VA|va|virginia|VI|vi|usvi|(u(\.|nited)?[ \t]*s(\.|tates)?[ \t]*)?virgin[ \t]+is(\.|lands)?|VT|vt|vermont|WA|wa|washington|WI|wi|wisconsin|WV|wv|west virginia|(w\.[ \t]*v\.)|WY|wy|wyoming)$ + type: string + - name: ZIPCode + in: query + description: This is the 5-digit ZIP code. + required: false + schema: + pattern: "^\\d{5}$" + type: string + - name: ZIPPlus4 + in: query + description: This is the 4-digit component of the ZIP+4 code. Using the correct Zip+4 reduces the number of times your mail is handled and can decrease the chance of a misdelivery or error. + required: false + schema: + pattern: "^\\d{4}$" + type: string + - name: urbanization + in: query + description: This is the urbanization code relevant only for Puerto Rico addresses. + required: false + schema: + type: string + responses: + "200": + description: Successful Operation. + content: + application/json: + schema: + $ref: '#/components/schemas/PickupAddress' + application/xml: + schema: + $ref: '#/components/schemas/PickupAddress' + "400": + description: Bad Request. There is an error in the received request. + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorMessage' + application/xml: + schema: + $ref: '#/components/schemas/ErrorMessage' + "401": + description: Unauthorized request. + headers: + WWW-Authenticate: + $ref: '#/components/headers/WWWAuthenticate' + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorMessage' + application/xml: + schema: + $ref: '#/components/schemas/ErrorMessage' + "403": + description: Access is denied. + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorMessage' + application/xml: + schema: + $ref: '#/components/schemas/ErrorMessage' + "429": + description: Too Many Requests. Too many requests have been received from client in a short amount of time. + headers: + Retry-After: + $ref: '#/components/headers/RetryAfter' + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorMessage' + application/xml: + schema: + $ref: '#/components/schemas/ErrorMessage' + "503": + description: Service is unavailable. + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorMessage' + application/xml: + schema: + $ref: '#/components/schemas/ErrorMessage' + default: + description: Other unanticipated errors that may occur. + content: {} + security: + - OAuth: + - carrier-pickup + /carrier-pickup: + post: + tags: + - Resources + summary: Schedule a carrier pickup. + description: |- + Schedule a carrier pickup on a specified date. + You can schedule pickups Monday - Saturday until 2:00 AM CT on the day of the pickup. After 2:00 AM CT, same-day pickup is not available. + If the address is eligible for carrier pickup, then you can schedule your pickup up to one year in advance. + operationId: post-carrier-pickup + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/SchedulePickupRequest' + application/xml: + schema: + $ref: '#/components/schemas/SchedulePickupRequest' + required: true + responses: + "200": + description: Successful Operation. + headers: + ETag: + $ref: '#/components/headers/ETag' + content: + application/json: + schema: + $ref: '#/components/schemas/PickupConfirmation' + application/xml: + schema: + $ref: '#/components/schemas/PickupConfirmation' + "400": + description: Bad Request. There is an error in the received request. + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorMessage' + application/xml: + schema: + $ref: '#/components/schemas/ErrorMessage' + "401": + description: Unauthorized request. + headers: + WWW-Authenticate: + $ref: '#/components/headers/WWWAuthenticate' + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorMessage' + application/xml: + schema: + $ref: '#/components/schemas/ErrorMessage' + "403": + description: Access is denied. + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorMessage' + application/xml: + schema: + $ref: '#/components/schemas/ErrorMessage' + "429": + description: Too Many Requests. Too many requests have been received from client in a short amount of time. + headers: + Retry-After: + $ref: '#/components/headers/RetryAfter' + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorMessage' + application/xml: + schema: + $ref: '#/components/schemas/ErrorMessage' + "503": + description: Service is unavailable. + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorMessage' + application/xml: + schema: + $ref: '#/components/schemas/ErrorMessage' + default: + description: Other unanticipated errors that may occur. + content: {} + security: + - OAuth: + - carrier-pickup + /carrier-pickup/{confirmationNumber}: + get: + tags: + - Resources + summary: Get a previously scheduled carrier pickup. + description: |- + Get the previously scheduled carrier pickup by confirmation number. + Responds with the entity tag (ETag) to use when updating or cancelling this pickup. + operationId: get-carrier-pickup-confirmation-number + parameters: + - name: confirmationNumber + in: path + description: This is the Carrier Pickup confirmation number. + required: true + style: simple + explode: false + schema: + type: string + responses: + "200": + description: Successful Operation. + headers: + ETag: + $ref: '#/components/headers/ETag' + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/PickupConfirmation' + application/xml: + schema: + type: array + items: + $ref: '#/components/schemas/PickupConfirmation' + "400": + description: Bad Request. There is an error in the received request. + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorMessage' + application/xml: + schema: + $ref: '#/components/schemas/ErrorMessage' + "401": + description: Unauthorized request. + headers: + WWW-Authenticate: + $ref: '#/components/headers/WWWAuthenticate' + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorMessage' + application/xml: + schema: + $ref: '#/components/schemas/ErrorMessage' + "403": + description: Access is denied. + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorMessage' + application/xml: + schema: + $ref: '#/components/schemas/ErrorMessage' + "429": + description: Too Many Requests. Too many requests have been received from client in a short amount of time. + headers: + Retry-After: + $ref: '#/components/headers/RetryAfter' + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorMessage' + application/xml: + schema: + $ref: '#/components/schemas/ErrorMessage' + "503": + description: Service is unavailable. + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorMessage' + application/xml: + schema: + $ref: '#/components/schemas/ErrorMessage' + default: + description: Other unanticipated errors that may occur. + content: {} + security: + - OAuth: + - carrier-pickup + put: + tags: + - Resources + summary: Update a previously scheduled carrier pickup. + description: "Update information contained in a previously scheduled carrier pickup such as the pickup date, the types and counts of packages for the carrier to pick up, the weight or the pickup location." + operationId: put-carrier-pickup-confirmation-number + parameters: + - name: confirmationNumber + in: path + description: This is the Carrier Pickup confirmation number. + required: true + style: simple + explode: false + schema: + type: string + - name: If-Match + in: header + description: The value of the entity tag indicating the version of the resource to get or update. All DELETE and PUT operations require an ETag to be supplied as the If-Match parameter. The ETag is returned in the GET operation and is good for one hour or until it is used in a DELETE or PUT operation. + required: true + schema: + type: string + example: 33a64df551425fcc55e4d42a148795d9f25f89d4 + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/PickupConfirmation' + application/xml: + schema: + $ref: '#/components/schemas/PickupConfirmation' + required: true + responses: + "200": + description: Successful Operation. + headers: + ETag: + $ref: '#/components/headers/ETag' + content: + application/json: + schema: + $ref: '#/components/schemas/PickupConfirmation' + application/xml: + schema: + $ref: '#/components/schemas/PickupConfirmation' + "400": + description: Bad Request. There is an error in the received request. + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorMessage' + application/xml: + schema: + $ref: '#/components/schemas/ErrorMessage' + "401": + description: Unauthorized request. + headers: + WWW-Authenticate: + $ref: '#/components/headers/WWWAuthenticate' + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorMessage' + application/xml: + schema: + $ref: '#/components/schemas/ErrorMessage' + "403": + description: Access is denied. + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorMessage' + application/xml: + schema: + $ref: '#/components/schemas/ErrorMessage' + "429": + description: Too Many Requests. Too many requests have been received from client in a short amount of time. + headers: + Retry-After: + $ref: '#/components/headers/RetryAfter' + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorMessage' + application/xml: + schema: + $ref: '#/components/schemas/ErrorMessage' + "503": + description: Service is unavailable. + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorMessage' + application/xml: + schema: + $ref: '#/components/schemas/ErrorMessage' + default: + description: Other unanticipated errors that may occur. + content: {} + security: + - OAuth: + - carrier-pickup + delete: + tags: + - Resources + summary: Cancel a previously scheduled carrier pickup. + description: |- + Cancel a previously scheduled carrier pick up. + A carrier pickup can no longer be updated or cancelled once cancelled. + A successful response indicates the carrier pickup has been cancelled. + operationId: delete-carrier-pickup-confirmation-number + parameters: + - name: confirmationNumber + in: path + description: This is the Carrier Pickup confirmation number. + required: true + style: simple + explode: false + schema: + type: string + - name: If-Match + in: header + description: The value of the entity tag indicating the version of the resource to get or update. All DELETE and PUT operations require an ETag to be supplied as the If-Match parameter. The ETag is returned in the GET operation and is good for one hour or until it is used in a DELETE or PUT operation. + required: true + schema: + type: string + example: 33a64df551425fcc55e4d42a148795d9f25f89d4 + responses: + "200": + description: Successful Operation. + "400": + description: Bad Request. There is an error in the received request. + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorMessage' + application/xml: + schema: + $ref: '#/components/schemas/ErrorMessage' + "401": + description: Unauthorized request. + headers: + WWW-Authenticate: + $ref: '#/components/headers/WWWAuthenticate' + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorMessage' + application/xml: + schema: + $ref: '#/components/schemas/ErrorMessage' + "403": + description: Access is denied. + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorMessage' + application/xml: + schema: + $ref: '#/components/schemas/ErrorMessage' + "429": + description: Too Many Requests. Too many requests have been received from client in a short amount of time. + headers: + Retry-After: + $ref: '#/components/headers/RetryAfter' + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorMessage' + application/xml: + schema: + $ref: '#/components/schemas/ErrorMessage' + "503": + description: Service is unavailable. + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorMessage' + application/xml: + schema: + $ref: '#/components/schemas/ErrorMessage' + default: + description: Other unanticipated errors that may occur. + content: {} + security: + - OAuth: + - carrier-pickup +components: + schemas: + SchedulePickupRequest: + title: Schedule Carrier Pickup Request + required: + - estimatedWeight + - packages + - pickupAddress + - pickupDate + - pickupLocation + type: object + properties: + pickupDate: + type: string + description: "This is the requested pickup date. You can schedule pickups Monday - Saturday until 2:00 AM CT on the day of the pickup. After 2:00 AM CT, same-day pickup is not available." + format: date + pickupAddress: + $ref: '#/components/schemas/PickupAddress' + packages: + type: array + description: The details on the types and amounts of packages ready for pick-up. + xml: + wrapped: true + items: + $ref: '#/components/schemas/SchedulePickupRequest_packages' + estimatedWeight: + type: number + description: Estimated aggregate weight (in pounds) of all packages ready for pick-up. + pickupLocation: + $ref: '#/components/schemas/SchedulePickupRequest_pickupLocation' + nextAvailablePickup: + type: boolean + description: "If you specify a pickup on a date when pickup service is not available. The API will automatically schedule your pickup for the next available day when pickup is available." + default: false + additionalProperties: false + description: The Schedule Carrier Pickup Request includes all of the necessary information to schedule a package pickup from your carrier. + PickupConfirmation: + title: Pickup Confirmation + type: object + properties: + confirmationNumber: + type: string + description: This is the USPS assigned confirmation number of the pick-up. + readOnly: true + pickupDate: + type: string + description: This is the scheduled pick-up date. + format: date + carrierPickupRequest: + $ref: '#/components/schemas/SchedulePickupRequest' + additionalProperties: false + xml: + name: pickupConfirmation + PickupAddress: + title: Pickup Address + required: + - contact + type: object + additionalProperties: false + description: This is the point of contact information for a potential pickup. + xml: + name: pickupAddress + allOf: + - $ref: '#/components/schemas/Addressee' + - type: object + required: + - address + properties: + address: + $ref: '#/components/schemas/DomesticAddress' + contact: + maxItems: 2 + minItems: 1 + type: array + description: One or more contact methods used to facilitate package pickup. + xml: + wrapped: true + items: + $ref: '#/components/schemas/NotificationMethod' + ErrorMessage: + title: Error + type: object + properties: + apiVersion: + type: string + description: The version of the API that was used and that raised the error. + error: + $ref: '#/components/schemas/ErrorMessage_error' + additionalProperties: true + description: Standard error message response. + Addressee: + title: Addressee + type: object + required: + - firstName + - lastName + properties: + firstName: + maxLength: 50 + minLength: 1 + type: string + description: This is the first name corresponding to the address. + lastName: + maxLength: 50 + minLength: 1 + type: string + description: This is the last name corresponding to the address. + firm: + maxLength: 50 + minLength: 0 + type: string + description: This is the firm corresponding to the address. Firm is required for all domestic addresses in international label requests due to customs form requirements. + additionalProperties: true + description: Fields used to identity the entity receiving a mail piece or package + DomesticAddress: + title: Domestic Address + additionalProperties: true + description: Address fields for US locations. Combinations of streetAddress with ZIPCode, City/State, or City/State/ZIPCode must be used for a valid pickup response. + allOf: + - $ref: '#/components/schemas/Address' + - type: object + required: + - streetAddress + properties: + city: + maxLength: 28 + minLength: 1 + type: string + description: This is the city name of the address. + state: + maxLength: 100 + minLength: 2 + pattern: ^(AA|aa|AE|ae|AP|ap|AL|al|alabama|AK|ak|alaska|AS|as|((am(\.|erican)?[ \t]+)?samoa)|AZ|az|arizona|AR|ar|arkansas|CA|ca|california|CO|co|colorado|CT|ct|connecticut|DC|dc|district of columbia|(d\.[ \t]*c\.)|DE|de|delaware|FL|fl|florida|FM|fm|((federated[ \t]+states[ \t]+of[ \t]+)?micronesia)|GA|ga|georgia|GU|gu|guam|HI|hi|hawaii|IA|ia|iowa|ID|id|idaho|IL|il|illinois|IN|in|indiana|KS|ks|kansas|KY|ky|kentucky|LA|la|louisiana|MA|ma|massachusetts|MD|md|maryland|ME|me|maine|MH|mh|(marshall[ \t]+is(\.|lands)?)|MI|mi|michigan|MN|mn|minnesota|MO|mo|missouri|MP|mp|((northern[ \t]+)?mariana(s|[ \t]+is(\.|lands)?))|MS|ms|mississippi|MT|mt|montana|NC|nc|north carolina|(n\.[ \t]*c\.)|ND|nd|north dakota|(n\.[ \t]*d\.)|NE|ne|nebraska|NH|nh|new hampshire|(n\.[ \t]*h\.)|NJ|nj|new jersey|(n\.[ \t]*j\.)|NM|nm|new mexico|(n\.[ \t]*m\.)|NV|nv|nevada|NY|ny|new york|(n\.[ \t]*y\.)|OH|oh|ohio|OK|ok|oklahoma|OR|or|oregon|PA|pa|pennsylvania|PR|pr|(p(\.|uerto)?[ \t]*r(\.|ico)?)|PW|pw|palau|RI|ri|rhode island|(r\.[ \t]*i\.)|SC|sc|south carolina|(s\.[ \t]*c\.)|SD|sd|south dakota|(s\.[ \t]*d\.)|TN|tn|tennessee|TX|tx|texas|UT|ut|utah|VA|va|virginia|VI|vi|usvi|(u(\.|nited)?[ \t]*s(\.|tates)?[ \t]*)?virgin[ \t]+is(\.|lands)?|VT|vt|vermont|WA|wa|washington|WI|wi|wisconsin|WV|wv|west virginia|(w\.[ \t]*v\.)|WY|wy|wyoming)$ + type: string + description: This is the state name or two-character state code of the address. The request accepts either the full state name or a two-character state code. The response will always return the two-character state code. + ZIPCode: + pattern: "^\\d{5}$" + type: string + description: This is the 5-digit ZIP code. + ZIPPlus4: + pattern: "^\\d{4}$" + type: string + description: This is the ZIP+4 extension. + nullable: true + urbanization: + maxLength: 96 + type: string + description: "An area, sector, or residential development within a geographic area (typically used for addresses in Puerto Rico)" + additionalProperties: true + Address: + title: Address + type: object + properties: + streetAddress: + maxLength: 50 + minLength: 1 + type: string + description: The number of a building along with the name of the road or street on which it is located. + streetAddressAbbreviation: + maxLength: 50 + minLength: 0 + type: string + description: This is the abbreviation of street address line for the address. + readOnly: true + secondaryAddress: + maxLength: 50 + type: string + description: "The secondary unit designator, such as apartment(APT) or suite(STE) number, defining the exact location of the address within a building. For more information please see [Postal Explorer](https://pe.usps.com/text/pub28/28c2_003.htm)." + cityAbbreviation: + type: string + description: This is the abbreviation of city name for the address. + readOnly: true + additionalProperties: true + description: Address fields standard to all locations. + xml: + name: Address + NotificationMethod: + type: object + oneOf: + - $ref: '#/components/schemas/EmailNotification' + - $ref: '#/components/schemas/SMSNotification' + EmailNotification: + type: object + properties: + email: + maxLength: 50 + type: string + description: 'E-mail address of recipient. Valid e-mail addresses must be used used for notifications via email. ' + format: email + SMSNotification: + type: object + properties: + cellNumber: + pattern: "^\\d{10}$" + type: string + description: "The 10 digit cell number, including the area code, and with no punctuation used for notifications via text messages. This field is for future Use \n " + SchedulePickupRequest_packages: + minItems: 1 + required: + - packageCount + - packageType + type: object + properties: + packageType: + type: string + description: This is the type of package ready for pick-up + enum: + - FIRST-CLASS_PACKAGE_SERVICE + - PRIORITY_MAIL_EXPRESS + - PRIORITY_MAIL + - RETURNS + - USPS_GROUND_ADVANTAGE + - INTERNATIONAL + - OTHER + packageCount: + type: integer + description: Number of packages ready for pick-up for the associated Mail Class. + additionalProperties: false + xml: + name: package + SchedulePickupRequest_pickupLocation: + required: + - packageLocation + type: object + properties: + packageLocation: + type: string + description: This is the location of the packages ready for pick-up at the pickup address. + enum: + - FRONT_DOOR + - BACK_DOOR + - SIDE_DOOR + - KNOCK_ON_DOOR + - MAIL_ROOM + - OFFICE + - PORCH + - RECEPTION + - MAILBOX + - OTHER + specialInstructions: + type: string + description: Additional details on where the packages can be picked-up. This is required when the package location is 'OTHER'. + dogPresent: + type: boolean + description: Used to notify the carrier if a dog is present at the pickup location. + default: false + additionalProperties: false + description: Details of where to pickup packages. + ErrorMessage_error_source: + type: object + properties: + parameter: + type: string + description: The input in the request which caused an error. + example: + type: string + description: An example of a valid value for the input parameter. + additionalProperties: true + description: The element that is suspected of originating the error. Helps to pinpoint the problem. + ErrorMessage_error_errors: + type: object + properties: + status: + type: string + description: The status code response returned to the client. + code: + type: string + description: An internal subordinate code used for error diagnosis. + title: + type: string + description: A human-readable title that identifies the error. + detail: + type: string + description: A human-readable description of the error that occurred. + source: + $ref: '#/components/schemas/ErrorMessage_error_source' + additionalProperties: true + ErrorMessage_error: + type: object + properties: + code: + type: string + description: The error status code that has been returned in response to the request. + message: + type: string + description: A human-readable message describing the error. + errors: + type: array + items: + $ref: '#/components/schemas/ErrorMessage_error_errors' + additionalProperties: true + description: The high-level error that has occurred as indicated by the status code. + parameters: + ConfirmationNumber: + name: confirmationNumber + in: path + description: This is the Carrier Pickup confirmation number. + required: true + style: simple + explode: false + schema: + type: string + StreetAddress-Required: + name: streetAddress + in: query + description: The number of a building along with the name of the road or street on which it is located. + required: true + schema: + type: string + SecondaryAddress: + name: secondaryAddress + in: query + description: "The secondary unit designator, such as apartment(APT) or suite(STE) number, defining the exact location of the address within a building. For more information please see [Postal Explorer](https://pe.usps.com/text/pub28/28c2_003.htm)." + required: false + schema: + type: string + City-Required: + name: city + in: query + description: This is the city name of the address. + required: true + schema: + type: string + State-Required: + name: state + in: query + description: This is the state name or two-character state code of the address. The request accepts either the full state name or a two-character state code. The response will always return the two-character state code. + required: true + schema: + maxLength: 100 + minLength: 2 + pattern: ^(AA|aa|AE|ae|AP|ap|AL|al|alabama|AK|ak|alaska|AS|as|((am(\.|erican)?[ \t]+)?samoa)|AZ|az|arizona|AR|ar|arkansas|CA|ca|california|CO|co|colorado|CT|ct|connecticut|DC|dc|district of columbia|(d\.[ \t]*c\.)|DE|de|delaware|FL|fl|florida|FM|fm|((federated[ \t]+states[ \t]+of[ \t]+)?micronesia)|GA|ga|georgia|GU|gu|guam|HI|hi|hawaii|IA|ia|iowa|ID|id|idaho|IL|il|illinois|IN|in|indiana|KS|ks|kansas|KY|ky|kentucky|LA|la|louisiana|MA|ma|massachusetts|MD|md|maryland|ME|me|maine|MH|mh|(marshall[ \t]+is(\.|lands)?)|MI|mi|michigan|MN|mn|minnesota|MO|mo|missouri|MP|mp|((northern[ \t]+)?mariana(s|[ \t]+is(\.|lands)?))|MS|ms|mississippi|MT|mt|montana|NC|nc|north carolina|(n\.[ \t]*c\.)|ND|nd|north dakota|(n\.[ \t]*d\.)|NE|ne|nebraska|NH|nh|new hampshire|(n\.[ \t]*h\.)|NJ|nj|new jersey|(n\.[ \t]*j\.)|NM|nm|new mexico|(n\.[ \t]*m\.)|NV|nv|nevada|NY|ny|new york|(n\.[ \t]*y\.)|OH|oh|ohio|OK|ok|oklahoma|OR|or|oregon|PA|pa|pennsylvania|PR|pr|(p(\.|uerto)?[ \t]*r(\.|ico)?)|PW|pw|palau|RI|ri|rhode island|(r\.[ \t]*i\.)|SC|sc|south carolina|(s\.[ \t]*c\.)|SD|sd|south dakota|(s\.[ \t]*d\.)|TN|tn|tennessee|TX|tx|texas|UT|ut|utah|VA|va|virginia|VI|vi|usvi|(u(\.|nited)?[ \t]*s(\.|tates)?[ \t]*)?virgin[ \t]+is(\.|lands)?|VT|vt|vermont|WA|wa|washington|WI|wi|wisconsin|WV|wv|west virginia|(w\.[ \t]*v\.)|WY|wy|wyoming)$ + type: string + ZIPCode: + name: ZIPCode + in: query + description: This is the 5-digit ZIP code. + required: false + schema: + pattern: "^\\d{5}$" + type: string + ZIPPlus4: + name: ZIPPlus4 + in: query + description: This is the 4-digit component of the ZIP+4 code. Using the correct Zip+4 reduces the number of times your mail is handled and can decrease the chance of a misdelivery or error. + required: false + schema: + pattern: "^\\d{4}$" + type: string + Urbanization: + name: urbanization + in: query + description: This is the urbanization code relevant only for Puerto Rico addresses. + required: false + schema: + type: string + IfMatch: + name: If-Match + in: header + description: The value of the entity tag indicating the version of the resource to get or update. All DELETE and PUT operations require an ETag to be supplied as the If-Match parameter. The ETag is returned in the GET operation and is good for one hour or until it is used in a DELETE or PUT operation. + required: true + schema: + type: string + example: 33a64df551425fcc55e4d42a148795d9f25f89d4 + headers: + WWWAuthenticate: + description: Hint to the client application which security scheme to authorize a resource request. + required: false + schema: + type: string + example: "WWW-Authenticate: Bearer realm=\"https://api.usps.com\"" + RetryAfter: + description: Indicate to the client application a time after which they can retry a resource request. + required: false + schema: + type: string + example: "Retry-After: 30" + ETag: + description: Entity Tag response header indicating version of resource. All DELETE and PUT operations require an ETag. The ETag is returned in the GET operation and is good for one hour or until it is used in a DELETE or PUT operation. + schema: + type: string + example: "ETag: \"33a64df551425fcc55e4d42a148795d9f25f89d4\"" + securitySchemes: + OAuth: + type: oauth2 + description: The specified APIs accept an access token formatted as a JSON Web Token. The relative path to the OAuth2 version 3 API which supplies this access token is provided below for reference. + flows: + clientCredentials: + tokenUrl: /oauth2/v3/token + scopes: + pickup: read write access to all pickup endpoints + authorizationCode: + authorizationUrl: /oauth2/v3/authorize + tokenUrl: /oauth2/v3/token + scopes: + pickup: read write access to all pickup endpoints diff --git a/modules/connectors/usps/vendor/domestic-labels.yaml b/modules/connectors/usps/vendor/domestic-labels.yaml new file mode 100644 index 0000000000..f0c7ef4b74 --- /dev/null +++ b/modules/connectors/usps/vendor/domestic-labels.yaml @@ -0,0 +1,6655 @@ +openapi: 3.0.0 +info: + title: Labels + description: | + Contact Us: [USPS API Support](https://emailus.usps.com/s/usps-APIs) | [Terms of Service](https://about.usps.com/termsofuse.htm) + + The USPS® Labels API allows you to digitally create mailing labels for the following mail classes: USPS Ground Advantage™, Parcel Select® Destination Entry, Parcel Select Lightweight®, USPS Connect® Local, USPS Connect® Regional, Priority + Mail®, Priority Mail Express®, Bound Printed Matter, Library Mail, and Media Mail®. + + * Use the [Getting Started guide](https://developers.usps.com/getting-started) to get started with the Labels API. + * The USPS® Labels API uses a token to authorize the use of an [Enterprise Payment](https://postalpro.usps.com/EPS) or Permit account. The specification to generate the token is in the [Payments 3.0 - Set Payment Account Information](https://developers.usps.com/paymentsv3#tag/Resources/operation/post-payments-payment-authorization). + + **Manifest Scheduling** + + The following table outlines the Domestic Label manifesting schedule for specific conditions listed below. + + | Condition | Scheduling | + | ----------------------------| -------- | + | **Return labels** | 12:30am CT on the day after label creation. | + | **Smart Locker labels** | Electronic data is sent to Postal systems within 15 minutes of label creation. | + | **Value of `true` provided for `immediateManifest` field**| Electronic data is sent to Postal systems within 15 minutes of label creation. | + | **Label is added to a [SCAN Form](https://developers.usps.com/scanv3#tag/Resources/operation/post-scan-form)**| Electronic data is sent to Postal systems within 15 minutes of SCAN Form creation. | + | **All other conditions** | 12:30am CT after the `mailingDate` value provided by the user | + version: 3.9.13 +servers: + - url: https://apis.usps.com/labels/v3 + description: Production Environment Endpoint + - url: https://apis-tem.usps.com/labels/v3 + description: Testing Environment Endpoint +paths: + /label: + post: + tags: + - Resources + summary: Create a domestic shipping label. + description: | + Generates a shipping label based upon the following: + + | Element | Description | + | ----------------------------- | --------| + | **toAddress** | Address where the package is being shipped to. The ZIP Code™ in the `toAddress` object is used to calculate pricing. | + | **fromAddress** | Address where the package is being shipped from. The ZIP Code™ in the `fromAddress` object is used to calculate pricing. | + | **senderAddress** | Address of the business involved. The `senderAddress` can be different from the `fromAddress` when the business address is not where the package is being shipped from. | + | **returnAddress** | Address where the package should be returned to if it is deemed undeliverable or returned to sender. This address will be printed in the return address block of the label. | + | **packageDescription** | Package characteristics used to price the shipment including requested mail class, weight, dimensions, and more. | + | **imageInfo** | Additional details used to determine how to generate the label image. | + | **customsForm** | Additional details required when creating shipments originating from or destinating to MPOs, APOs, FPOs, or DPOs (Military, Army, Fleet, and Diplomatic Post Offices) and some Military and U.S., Possessions, Territories, and Freely Associated States (PTFAS).| + + The default media type of the response has multiple parts. Setting the Accept header to either application/json or application/xml will only affect the metadata part of the multipart response. + + **How to use this API** + + The metadata part of the multipart response representation is only in application/json format media type, as default. The application/xml media type is not supported at this time. + + Ignore the Content-Encoding header in the second part, the label image file. The contents of this part are not compressed. + + Ignore the Content-Transfer-Encoding header in the second part, the label image file. The contents of this part are always Base64 encoded. + + Ignore the Content-Encoding header in the third part, the receipt image file. The contents of this part are not compressed. + + Ignore the Content-Transfer-Encoding header in the third part, the receipt image file. The contents of this part are always Base64 encoded. + + **Instructions** + + Extract the first part of the multipart response as application/json media type. + + Extract the second part of the multipart response and Base64 decode it to yield the binary image file. + + Save the binary image file, given the Content-Type header value and the filename of this part (e.g. application/pdf, label.pdf). It is recommended that you run a virus scan on the resulting downloaded file. + + Extract the third part of the multipart response and Base64 decode it to yield the binary image file. + + Save the binary image file, given the Content-Type header value and the filename of this part (e.g. application/pdf, receipt.pdf). It is recommended that you run a virus scan on the resulting downloaded file. + operationId: post-label + parameters: + - $ref: "#/components/parameters/X-Payment-Authorization-Token" + - $ref: "#/components/parameters/X-Idempotency-Key" + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/LabelRequest' + examples: + GroundAdvantageRequest: + $ref: '#/components/examples/GroundAdvantageRequest' + GroundAdvantageRequestWithImageInfo: + $ref: '#/components/examples/GroundAdvantageRequestWithImageInfo' + HoldForPickupRequest: + $ref: '#/components/examples/HoldForPickupRequest' + PMECrematedRemainsRequest: + $ref: '#/components/examples/PMECrematedRemainsRequest' + CustomsFormRequest: + $ref: '#/components/examples/CustomsFormRequest' + SmartLockerRequest: + $ref: '#/components/examples/SmartLockerRequest' + LabelBrokerRequest: + $ref: '#/components/examples/LabelBrokerRequest' + application/xml: + schema: + $ref: '#/components/schemas/LabelRequest' + required: true + responses: + "200": + description: Successful operation. + content: + multipart/form-data: + schema: + $ref: '#/components/schemas/LabelMultiPartResponse' + encoding: + labelMetadata: + contentType: application/json + headers: + Content-Disposition: + style: simple + explode: false + schema: + type: string + example: "Content-Disposition: form-data; name=\"labelMetadata\"" + style: form + labelImage: + contentType: "application/octet-stream, application/pdf, image/jpg, image/png, image/gif, image/tiff, image/svg+xml" + headers: + Content-Disposition: + description: Additional information on how the client should handle the content. + style: simple + explode: false + schema: + type: string + example: "Content-Disposition: attachment; filename=\"label-image.jpg\"" + style: form + returnLabelMetadata: + contentType: application/json + headers: + Content-Disposition: + style: simple + explode: false + schema: + type: string + example: "Content-Disposition: form-data; name=\"returnLabelMetadata\"" + style: form + returnLabelImage: + contentType: "application/octet-stream, application/pdf, image/jpg, image/png, image/gif, image/tiff, image/svg+xml" + headers: + Content-Disposition: + description: Additional information on how the client should handle the content. + style: simple + explode: false + schema: + type: string + example: "Content-Disposition: attachment; filename=\"label-image.jpg\"" + style: form + receiptImage: + contentType: "application/octet-stream, application/pdf, image/jpg, image/png, image/gif, image/tiff, image/svg+xml" + headers: + Content-Disposition: + description: Additional information on how the client should handle the content. + style: simple + explode: false + schema: + type: string + example: "Content-Disposition: attachment; filename=\"receipt-image.jpg\"" + style: form + returnReceiptImage: + contentType: "application/octet-stream, application/pdf, image/jpg, image/png, image/gif, image/tiff, image/svg+xml" + headers: + Content-Disposition: + description: Additional information on how the client should handle the content. + style: simple + explode: false + schema: + type: string + example: "Content-Disposition: attachment; filename=\"receipt-image.jpg\"" + style: form + labelBrokerQR: + contentType: "image/png" + headers: + Content-Disposition: + description: Label Broker QR Code image, returned if `imageType` of `LABEL_BROKER` was requested and `imageInfo.includeLabelBrokerPDF` is `true`. + style: simple + explode: false + schema: + type: string + example: "Content-Disposition: attachment; filename=\"label-broker-qr.png\"" + style: form + examples: + style: form + application/vnd.usps.labels+json: + schema: + $ref: '#/components/schemas/LabelVendorResponse' + examples: + Base64EncodedImagesExample: + $ref: '#/components/examples/Base64EncodedImagesExample' + application/vnd.usps.labels+xml: + schema: + $ref: '#/components/schemas/LabelVendorResponse' + examples: + Base64EncodedImagesXMLExample: + $ref: '#/components/examples/Base64EncodedImagesXMLExample' + application/json: + schema: + $ref: '#/components/schemas/LabelMultiPartResponse' + encoding: + labelMetadata: + contentType: application/json + headers: + Content-Disposition: + style: simple + explode: false + schema: + type: string + example: "Content-Disposition: form-data; name=\"labelMetadata\"" + style: form + labelImage: + contentType: "application/octet-stream, application/pdf, image/jpg, image/png, image/gif, image/tiff, image/svg+xml" + headers: + Content-Disposition: + description: Additional information on how the client should handle the content. + style: simple + explode: false + schema: + type: string + example: "Content-Disposition: attachment; filename=\"label-image.jpg\"" + style: form + returnLabelMetadata: + contentType: application/json + headers: + Content-Disposition: + style: simple + explode: false + schema: + type: string + example: "Content-Disposition: form-data; name=\"returnLabelMetadata\"" + style: form + returnLabelImage: + contentType: "application/octet-stream, application/pdf, image/jpg, image/png, image/gif, image/tiff, image/svg+xml" + headers: + Content-Disposition: + description: Additional information on how the client should handle the content. + style: simple + explode: false + schema: + type: string + example: "Content-Disposition: attachment; filename=\"label-image.jpg\"" + style: form + receiptImage: + contentType: "application/octet-stream, application/pdf, image/jpg, image/png, image/gif, image/tiff, image/svg+xml" + headers: + Content-Disposition: + description: Additional information on how the client should handle the content. + style: simple + explode: false + schema: + type: string + example: "Content-Disposition: attachment; filename=\"receipt-image.jpg\"" + style: form + returnReceiptImage: + contentType: "application/octet-stream, application/pdf, image/jpg, image/png, image/gif, image/tiff, image/svg+xml" + headers: + Content-Disposition: + description: Additional information on how the client should handle the content. + style: simple + explode: false + schema: + type: string + example: "Content-Disposition: attachment; filename=\"receipt-image.jpg\"" + style: form + labelBrokerQR: + contentType: "image/png" + headers: + Content-Disposition: + description: Label Broker QR Code image, returned if `imageType` of `LABEL_BROKER` was requested and `imageInfo.includeLabelBrokerPDF` is `true`. + style: simple + explode: false + schema: + type: string + example: "Content-Disposition: attachment; filename=\"label-broker-qr.png\"" + style: form + examples: + style: form + application/xml: + schema: + $ref: '#/components/schemas/LabelMultiPartResponse' + encoding: + labelMetadata: + contentType: application/xml + headers: + Content-Disposition: + style: simple + explode: false + schema: + type: string + example: "Content-Disposition: form-data; name=\"labelMetadata\"" + style: form + labelImage: + contentType: "application/octet-stream, application/pdf, image/jpg, image/png, image/gif, image/tiff, image/svg+xml" + headers: + Content-Disposition: + description: Additional information on how the client should handle the content. + style: simple + explode: false + schema: + type: string + example: "Content-Disposition: attachment; filename=\"label-image.jpg\"" + style: form + returnLabelMetadata: + contentType: application/xml + headers: + Content-Disposition: + style: simple + explode: false + schema: + type: string + example: "Content-Disposition: form-data; name=\"returnLabelMetadata\"" + style: form + returnLabelImage: + contentType: "application/octet-stream, application/pdf, image/jpg, image/png, image/gif, image/tiff, image/svg+xml" + headers: + Content-Disposition: + description: Additional information on how the client should handle the content. + style: simple + explode: false + schema: + type: string + example: "Content-Disposition: attachment; filename=\"label-image.jpg\"" + style: form + receiptImage: + contentType: "application/octet-stream, application/pdf, image/jpg, image/png, image/gif, image/tiff, image/svg+xml" + headers: + Content-Disposition: + description: Additional information on how the client should handle the content. + style: simple + explode: false + schema: + type: string + example: "Content-Disposition: attachment; filename=\"receipt-image.jpg\"" + style: form + returnReceiptImage: + contentType: "application/octet-stream, application/pdf, image/jpg, image/png, image/gif, image/tiff, image/svg+xml" + headers: + Content-Disposition: + description: Additional information on how the client should handle the content. + style: simple + explode: false + schema: + type: string + example: "Content-Disposition: attachment; filename=\"receipt-image.jpg\"" + style: form + labelBrokerQR: + contentType: "image/png" + headers: + Content-Disposition: + description: Label Broker QR Code image, returned if `imageType` of `LABEL_BROKER` was requested and `imageInfo.includeLabelBrokerPDF` is `true`. + style: simple + explode: false + schema: + type: string + example: "Content-Disposition: attachment; filename=\"label-broker-qr.png\"" + style: form + examples: + style: form + "400": + description: Bad Request. There is an error in the received request. + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorMessage' + application/xml: + schema: + $ref: '#/components/schemas/ErrorMessage' + "401": + description: Unauthorized request. + headers: + WWW-Authenticate: + $ref: '#/components/headers/WWWAuthenticate' + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorMessage' + application/xml: + schema: + $ref: '#/components/schemas/ErrorMessage' + "403": + description: Access is denied. + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorMessage' + application/xml: + schema: + $ref: '#/components/schemas/ErrorMessage' + "429": + description: Too Many Requests. Too many requests have been received from the client in a short amount of time. + headers: + Retry-After: + $ref: '#/components/headers/RetryAfter' + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorMessage' + application/xml: + schema: + $ref: '#/components/schemas/ErrorMessage' + "503": + description: Service is unavailable. + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorMessage' + application/xml: + schema: + $ref: '#/components/schemas/ErrorMessage' + default: + description: Other unanticipated errors that may occur. + content: { } + security: + - OAuth: + - labels + delete: + tags: + - Resources + summary: Cancel a previously requested label or request a refund on label by providing the X-Idempotency-Key header + description: | + Cancel a label or submit a refund request by providing the X-Idempotency-Key header used to create the label. + + A label can be canceled if a Shipping Services File has not been created. If a Shipping Services File has been created, submit a refund request for an unused label. A label is eligible for a refund if: + - If the label has had a Shipping Services File created for it + - The label is not from Click-N-Ship or PC Postage + - Is a SCAN Form label that has had its Shipping Services File created + + Note: + - If the label is canceled, a Shipping Services File will not be created. + - If a refund request is successfully submitted, a disputeId will be returned that can be used to then track the status of the refund request. The system will only give one unique disputeId per customer reference ID (CRID) per day. Duplicate submissions of the same label, for the same CRID, on the same day, will be rejected. + operationId: delete-label-by-idempotency + parameters: + - $ref: "#/components/parameters/X-Payment-Authorization-Token" + - $ref: "#/components/parameters/X-Idempotency-Key" + responses: + "200": + description: Successful Operation + headers: + X-Idempotency-Key: + description: The idempotency key associated with the original label creation request. + schema: + type: string + format: uuid + example: 8be4df61-93ca-11d2-aa0d-00e098032b8c + content: + application/json: + schema: + $ref: '#/components/schemas/CancelResponse' + examples: + Label Canceled: + $ref: '#/components/examples/ExampleCancel_CANCELED' + Refund Submitted: + $ref: '#/components/examples/ExampleCancel_REFUND' + application/xml: + schema: + $ref: '#/components/schemas/CancelResponse' + examples: + Label Canceled: + $ref: '#/components/examples/ExampleCancel_CANCELED' + Refund Submitted: + $ref: '#/components/examples/ExampleCancel_REFUND' + "400": + description: Bad Request. There is an error in the received request. + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorMessage' + examples: + Not Found Error: + $ref: '#/components/examples/ExampleCancelError_NOTFOUND' + Refund Not Submitted: + $ref: '#/components/examples/ExampleCancelError_REFUND_NOT_SUBMITTED' + Refund Same Label Same Day: + $ref: '#/components/examples/ExampleCancelError_REFUND_SAMELABEL_SAMEDAY' + application/xml: + schema: + $ref: '#/components/schemas/ErrorMessage' + examples: + Not Found Error: + $ref: '#/components/examples/ExampleCancelError_NOTFOUND' + Refund Not Submitted: + $ref: '#/components/examples/ExampleCancelError_REFUND_NOT_SUBMITTED' + Refund Same Label Same Day: + $ref: '#/components/examples/ExampleCancelError_REFUND_SAMELABEL_SAMEDAY' + "401": + description: Unauthorized request. + headers: + WWW-Authenticate: + $ref: '#/components/headers/WWWAuthenticate' + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorMessage' + application/xml: + schema: + $ref: '#/components/schemas/ErrorMessage' + "403": + description: Access is denied. + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorMessage' + application/xml: + schema: + $ref: '#/components/schemas/ErrorMessage' + "429": + description: Too Many Requests. Too many requests have been received from the client in a short amount of time. + headers: + Retry-After: + $ref: '#/components/headers/RetryAfter' + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorMessage' + application/xml: + schema: + $ref: '#/components/schemas/ErrorMessage' + "503": + description: Service is unavailable. + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorMessage' + application/xml: + schema: + $ref: '#/components/schemas/ErrorMessage' + default: + description: Other unanticipated errors that may occur. + content: { } + security: + - OAuth: + - labels + /return-label: + post: + tags: + - Resources + summary: Create a domestic returns shipping label. + description: | + Generates a returns shipping label based upon the input dimensions requested. + + | Element | Description | + | ------------------------------ | -------- | + | **toAddress** | Address where the return package is being shipped to. The ZIP Code™ in the `toAddress` object is used to calculate pricing. | + | **fromAddress** | Address where the return package is being shipped from. The ZIP Code™ in the `fromAddress` object is used to calculate pricing. | + | **senderAddress** | Address of the business involved (typically the sender of the original package). The `senderAddress` can be different from the `toAddress` when the business address is not where the package is being shipped to. | + | **returnAddress** | Address where the package should be returned to if it is deemed undeliverable or returned to sender. This address will be printed in the return address block of the label. This could be a secondary return address in case the `toAddress` is deemed undeliverable or returned to sender. | + | **packageDescription** | Package characteristics used to price the shipment including requested mail class, weight, dimensions, and more. | + | **imageInfo** | Additional details used to update label characteristics | + | **customsForm** | Additional details required when creating shipments originating from or destinating to MPOs, APOs, FPOs, or DPOs (Military, Army, Fleet, and Diplomatic Post Offices) and some Military and U.S. Possessions, Territories, and Freely Associated States (PTFAS).| + + + The default media type of the response has multiple parts. Setting the Accept header to either + `application/json` or `application/xml` will only affect the metadata part of the multipart response. + + **How to use this API** + + The metadata part of the multipart response representation is only in application/json + format media type, as default. + + Ignore the Content-Encoding header in the second part, the label image file. The contents of this part are not compressed. + + + Ignore the Content-Transfer-Encoding header in the second part, the label image file. The contents of this part are always Base64 encoded. + + + Ignore the Content-Encoding header in the third part, the receipt image file. The contents of this part are not compressed. + + + Ignore the Content-Transfer-Encoding header in the third part, the receipt image file. The contents of this part are always Base64 encoded. + + **Instructions** + + Extract the first part of the multipart response as application/json media type. + + Extract the second part of the multipart response and Base64 decode it to yield the binary image file. + + Save the binary image file, given the Content-Type header value and the filename of this part (e.g. application/pdf, label.pdf). It is recommended that you run a virus scan on the resulting downloaded file. + + Extract the third part of the multipart response and Base64 decode it to yield the binary image file. + + Save the binary image file, given the Content-Type header value and the filename of this part (e.g. application/pdf, receipt.pdf). It is recommended that you run a virus scan on the resulting downloaded file. + + operationId: post-return-label + parameters: + - $ref: "#/components/parameters/X-Payment-Authorization-Token" + - $ref: "#/components/parameters/X-Idempotency-Key" + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/ReturnLabelRequest' + examples: + GroundAdvantageReturnLabelRequest: + $ref: '#/components/examples/GroundAdvantageReturnLabelRequest' + application/xml: + schema: + $ref: '#/components/schemas/ReturnLabelRequest' + required: true + responses: + "200": + description: Success + content: + multipart/form-data: + schema: + $ref: '#/components/schemas/ReturnLabelMultiPartResponse' + encoding: + returnLabelMetadata: + contentType: application/json + headers: + Content-Disposition: + style: simple + explode: false + schema: + type: string + example: "Content-Disposition: form-data; name=\"returnLabelMetadata\"" + style: form + returnLabelImage: + contentType: "application/octet-stream, application/pdf, image/jpg, image/png, image/gif, image/tiff, image/svg+xml" + headers: + Content-Disposition: + description: Additional information on how the client should handle the content. + style: simple + explode: false + schema: + type: string + example: "Content-Disposition: attachment; filename=\"label-image.jpg\"" + style: form + returnReceiptImage: + contentType: "application/octet-stream, application/pdf, image/jpg, image/png, image/gif, image/tiff, image/svg+xml" + headers: + Content-Disposition: + description: Additional information on how the client should handle the content. + style: simple + explode: false + schema: + type: string + example: "Content-Disposition: attachment; filename=\"receipt-image.jpg\"" + style: form + labelBrokerQR: + contentType: "image/png" + headers: + Content-Disposition: + description: Label Broker QR Code image, returned if `imageType` of `LABEL_BROKER` was requested and `imageInfo.includeLabelBrokerPDF` is `true`. + style: simple + explode: false + schema: + type: string + example: "Content-Disposition: attachment; filename=\"label-broker-qr.png\"" + style: form + application/vnd.usps.labels+json: + schema: + $ref: '#/components/schemas/ReturnLabelVendorResponse' + examples: + JSONReturnLabelWithEmbeddedBased64Response: + $ref: '#/components/examples/JSONReturnLabelWithEmbeddedBased64Response' + application/vnd.usps.labels+xml: + schema: + $ref: '#/components/schemas/ReturnLabelVendorResponse' + examples: + XMLReturnLabelWithEmbeddedBased64Response: + $ref: '#/components/examples/XMLReturnLabelResponseWithEmbeddedBase64EncodedImages' + application/json: + schema: + $ref: '#/components/schemas/ReturnLabelMultiPartResponse' + encoding: + returnLabelMetadata: + contentType: application/json + headers: + Content-Disposition: + style: simple + explode: false + schema: + type: string + example: "Content-Disposition: form-data; name=\"returnLabelMetadata\"" + style: form + returnLabelImage: + contentType: "application/octet-stream, application/pdf, image/jpg, image/png, image/gif, image/tiff, image/svg+xml" + headers: + Content-Disposition: + description: Additional information on how the client should handle the content. + style: simple + explode: false + schema: + type: string + example: "Content-Disposition: attachment; filename=\"label-image.jpg\"" + style: form + returnReceiptImage: + contentType: "application/octet-stream, application/pdf, image/jpg, image/png, image/gif, image/tiff, image/svg+xml" + headers: + Content-Disposition: + description: Additional information on how the client should handle the content. + style: simple + explode: false + schema: + type: string + example: "Content-Disposition: attachment; filename=\"receipt-image.jpg\"" + style: form + labelBrokerQR: + contentType: "image/png" + headers: + Content-Disposition: + description: Label Broker QR Code image, returned if `imageType` of `LABEL_BROKER` was requested and `imageInfo.includeLabelBrokerPDF` is `true`. + style: simple + explode: false + schema: + type: string + example: "Content-Disposition: attachment; filename=\"label-broker-qr.png\"" + style: form + application/xml: + schema: + $ref: '#/components/schemas/ReturnLabelMultiPartResponse' + encoding: + returnLabelMetadata: + contentType: application/json + headers: + Content-Disposition: + style: simple + explode: false + schema: + type: string + example: "Content-Disposition: form-data; name=\"returnLabelMetadata\"" + style: form + returnLabelImage: + contentType: "application/octet-stream, application/pdf, image/jpg, image/png, image/gif, image/tiff, image/svg+xml" + headers: + Content-Disposition: + description: Additional information on how the client should handle the content. + style: simple + explode: false + schema: + type: string + example: "Content-Disposition: attachment; filename=\"label-image.jpg\"" + style: form + returnReceiptImage: + contentType: "application/octet-stream, application/pdf, image/jpg, image/png, image/gif, image/tiff, image/svg+xml" + headers: + Content-Disposition: + description: Additional information on how the client should handle the content. + style: simple + explode: false + schema: + type: string + example: "Content-Disposition: attachment; filename=\"receipt-image.jpg\"" + style: form + labelBrokerQR: + contentType: "image/png" + headers: + Content-Disposition: + description: Label Broker QR Code image, returned if `imageType` of `LABEL_BROKER` was requested and `imageInfo.includeLabelBrokerPDF` is `true`. + style: simple + explode: false + schema: + type: string + example: "Content-Disposition: attachment; filename=\"label-broker-qr.png\"" + style: form + "400": + description: Bad Request. There is an error in the received request. + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorMessage' + application/xml: + schema: + $ref: '#/components/schemas/ErrorMessage' + "401": + description: Unauthorized request. + headers: + WWW-Authenticate: + $ref: '#/components/headers/WWWAuthenticate' + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorMessage' + application/xml: + schema: + $ref: '#/components/schemas/ErrorMessage' + "403": + description: Access is denied. + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorMessage' + application/xml: + schema: + $ref: '#/components/schemas/ErrorMessage' + "429": + description: Too Many Requests. Too many requests have been received from the client in a short amount of time. + headers: + Retry-After: + $ref: '#/components/headers/RetryAfter' + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorMessage' + application/xml: + schema: + $ref: '#/components/schemas/ErrorMessage' + "503": + description: Service is unavailable. + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorMessage' + application/xml: + schema: + $ref: '#/components/schemas/ErrorMessage' + default: + description: Other unanticipated errors that may occur. + content: { } + security: + - OAuth: + - labels + /label/{trackingNumber}: + delete: + tags: + - Resources + summary: Cancel a previously requested label or request a refund on label by providing it's tracking number + description: | + Cancel a label or submit a refund request by providing the label's tracking number. + + A label can be canceled if a Shipping Services File has not been created. If a Shipping Services File has been created, submit a refund request for an unused label. A label is eligible for a refund if: + - If the label has had a Shipping Services File created for it + - The label is not from Click-N-Ship or PC Postage + - Is a SCAN Form label that has had its Shipping Services File created + + Note: + - If the label is canceled, a Shipping Services File will not be created. + - If a refund request is successfully submitted, a disputeId will be returned that can be used to then track the status of the refund request. The system will only give one unique disputeId per customer reference ID (CRID) per day. Duplicate submissions of the same label, for the same CRID, on the same day, will be rejected. + operationId: delete-label-by-tracking + parameters: + - $ref: "#/components/parameters/TrackingNumber" + - $ref: "#/components/parameters/X-Payment-Authorization-Token" + responses: + "200": + description: Successful Operation + content: + application/json: + schema: + $ref: '#/components/schemas/CancelResponse' + examples: + Label Canceled: + $ref: '#/components/examples/ExampleCancel_CANCELED' + Refund Submitted: + $ref: '#/components/examples/ExampleCancel_REFUND' + application/xml: + schema: + $ref: '#/components/schemas/CancelResponse' + examples: + Label Canceled: + $ref: '#/components/examples/ExampleCancel_CANCELED' + Refund Submitted: + $ref: '#/components/examples/ExampleCancel_REFUND' + "400": + description: Bad Request. There is an error in the received request. + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorMessage' + examples: + Not Found Error: + $ref: '#/components/examples/ExampleCancelError_NOTFOUND' + Refund Not Submitted: + $ref: '#/components/examples/ExampleCancelError_REFUND_NOT_SUBMITTED' + Refund Same Label Same Day: + $ref: '#/components/examples/ExampleCancelError_REFUND_SAMELABEL_SAMEDAY' + application/xml: + schema: + $ref: '#/components/schemas/ErrorMessage' + examples: + Not Found Error: + $ref: '#/components/examples/ExampleCancelError_NOTFOUND' + Refund Not Submitted: + $ref: '#/components/examples/ExampleCancelError_REFUND_NOT_SUBMITTED' + Refund Same Label Same Day: + $ref: '#/components/examples/ExampleCancelError_REFUND_SAMELABEL_SAMEDAY' + "401": + description: Unauthorized request. + headers: + WWW-Authenticate: + $ref: '#/components/headers/WWWAuthenticate' + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorMessage' + application/xml: + schema: + $ref: '#/components/schemas/ErrorMessage' + "403": + description: Access is denied. + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorMessage' + application/xml: + schema: + $ref: '#/components/schemas/ErrorMessage' + "429": + description: Too Many Requests. Too many requests have been received from the client in a short amount of time. + headers: + Retry-After: + $ref: '#/components/headers/RetryAfter' + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorMessage' + application/xml: + schema: + $ref: '#/components/schemas/ErrorMessage' + "503": + description: Service is unavailable. + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorMessage' + application/xml: + schema: + $ref: '#/components/schemas/ErrorMessage' + default: + description: Other unanticipated errors that may occur. + content: { } + security: + - OAuth: + - labels + patch: + tags: + - Resources + summary: Edit label attributes. + description: | + Allow customers to edit package attributes for previously created labels including weight, dimensions, rate indicator, processing category and containers. These fields eligible for editing will not impact label images, so previous label images can still be used. + + Changing these rate ingredients may affect the prices of the label. + Therefore, the Payment Authorization token is required. + + Note: Label edits will not be supported for the following scenarios; instead, unused label refunds should be requested and new labels should be created. + * All label edits are disallowed if the original label was created with `suppressPostage = false`. + * No dimensional updates are supported for Cubic Soft Pack labels. + * Cubic labels cannot be edited to non-cubic rate indicators. + operationId: patch-label-edit + parameters: + - $ref: "#/components/parameters/TrackingNumber" + - $ref: "#/components/parameters/X-Payment-Authorization-Token" + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/PatchRequest' + examples: + Labels Update: + $ref: '#/components/examples/ExamplePatch' + Labels Update3: + $ref: '#/components/examples/ExamplePatch3' + application/xml: + schema: + $ref: '#/components/schemas/PatchRequest' + examples: + Meters Update: + $ref: '#/components/examples/ExamplePatch' + Labels Update2: + $ref: '#/components/examples/ExamplePatch3' + required: true + responses: + "200": + description: Success + headers: + ETag: + $ref: '#/components/headers/ETag' + content: + application/json: + schema: + $ref: '#/components/schemas/LabelMetadata' + examples: + Edit Weight Response: + $ref: '#/components/examples/EditResponse' + application/xml: + schema: + $ref: '#/components/schemas/LabelMetadata' + "400": + description: Bad Request. There is an error in the received request. + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorMessage' + application/xml: + schema: + $ref: '#/components/schemas/ErrorMessage' + "401": + description: Unauthorized request. + headers: + WWW-Authenticate: + $ref: '#/components/headers/WWWAuthenticate' + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorMessage' + application/xml: + schema: + $ref: '#/components/schemas/ErrorMessage' + "403": + description: Access is denied. + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorMessage' + application/xml: + schema: + $ref: '#/components/schemas/ErrorMessage' + "429": + description: Too Many Requests. Too many requests have been received from the client in a short amount of time. + headers: + Retry-After: + $ref: '#/components/headers/RetryAfter' + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorMessage' + application/xml: + schema: + $ref: '#/components/schemas/ErrorMessage' + "503": + description: Service is unavailable. + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorMessage' + application/xml: + schema: + $ref: '#/components/schemas/ErrorMessage' + default: + description: Other unanticipated errors that may occur. + content: { } + security: + - OAuth: + - labels + /indicia: + post: + tags: + - Resources + summary: Create a First-Class letter, flat, or card indicia. + description: | + Generates a First-Class indicia for use on letter, flat, or card mailings. + + Notes: + - Only supported for 'PAYER' Roles with an EPS accountType. + - Indicia should be placed in the top right corner of letters, flats, and cards in order to avoid delays in processing. + - An Indicia created using this endpoint is non-refundable and cannot be refunded using the DELETE /indicia/imb endpoint. + operationId: post-label-indicia + parameters: + - $ref: "#/components/parameters/X-Payment-Authorization-Token" + requestBody: + description: Indicia request payload containing necessary data for generating a First-Class indicia. + content: + application/json: + schema: + $ref: '#/components/schemas/IndiciaRequest' + application/xml: + schema: + $ref: '#/components/schemas/IndiciaRequest' + required: true + responses: + "201": + description: Success + content: + multipart/form-data: + schema: + $ref: '#/components/schemas/IndiciaResponse' + encoding: + indiciaMetadata: + contentType: application/json + headers: + Content-Disposition: + style: simple + explode: false + schema: + type: string + example: "Content-Disposition: form-data; name=\"indiciaMetadata\"" + style: form + indiciaImage: + contentType: "application/pdf, image/jpg, image/png, image/gif, image/tiff, image/svg+xml" + headers: + Content-Disposition: + description: Additional information on how the client should handle the content. + style: simple + explode: false + schema: + type: string + example: "Content-Disposition: attachment; filename=\"indicia-image.jpg\"" + style: form + receiptImage: + contentType: "application/pdf, image/jpg, image/png, image/gif, image/tiff, image/svg+xml" + headers: + Content-Disposition: + description: Additional information on how the client should handle the content. + style: simple + explode: false + schema: + type: string + example: "Content-Disposition: attachment; filename=\"receipt-image.jpg\"" + style: form + examples: + style: form + application/json: + schema: + $ref: '#/components/schemas/IndiciaResponse' + encoding: + metadata: + contentType: application/json + style: form + examples: + style: form + application/xml: + schema: + $ref: '#/components/schemas/IndiciaResponse' + encoding: + metadata: + contentType: application/xml + style: form + examples: + style: form + application/vnd.usps.labels+json: + schema: + $ref: '#/components/schemas/IndiciaResponse' + examples: + IndiciaExampleResponse: + $ref: '#/components/examples/IndiciaExampleResponse' + application/vnd.usps.labels+xml: + schema: + $ref: '#/components/schemas/IndiciaResponse' + "400": + description: Bad Request. There is an error in the received request. + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorMessage' + application/xml: + schema: + $ref: '#/components/schemas/ErrorMessage' + "401": + description: Unauthorized request. + headers: + WWW-Authenticate: + $ref: '#/components/headers/WWWAuthenticate' + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorMessage' + application/xml: + schema: + $ref: '#/components/schemas/ErrorMessage' + "403": + description: Access is denied. + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorMessage' + application/xml: + schema: + $ref: '#/components/schemas/ErrorMessage' + "429": + description: Too Many Requests. Too many requests have been received from the client in a short amount of time. + headers: + Retry-After: + $ref: '#/components/headers/RetryAfter' + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorMessage' + application/xml: + schema: + $ref: '#/components/schemas/ErrorMessage' + "503": + description: Service is unavailable. + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorMessage' + application/xml: + schema: + $ref: '#/components/schemas/ErrorMessage' + default: + description: Other unanticipated errors that may occur. + content: { } + security: + - OAuth: + - labels + /indicia/imb: + post: + tags: + - Resources + summary: Create an Intelligent Mail Barcode (IMB) label for First-Class letters, flats, and cards. + description: | + Generates an Intelligent Mail Barcode (IMB) label for First-Class letters, flats, and cards. [See specification USPS-B-3200](https://postalpro.usps.com/node/2190). + + Notes: + - Only supported for 'PAYER' Roles with an EPS accountType. + - When IMB labels are placed on letters, flats, or cards that are larger than the label itself, the label should be placed in the top right corner in order to avoid delays in processing. + operationId: post-indicia-imb + parameters: + - $ref: "#/components/parameters/X-Payment-Authorization-Token" + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/IndiciaImbRequest' + application/xml: + schema: + $ref: '#/components/schemas/IndiciaImbRequest' + required: true + responses: + "201": + description: Success + content: + multipart/form-data: + schema: + $ref: '#/components/schemas/IndiciaImbResponse' + encoding: + indiciaMetadata: + contentType: application/json + headers: + Content-Disposition: + style: simple + explode: false + schema: + type: string + example: "Content-Disposition: form-data; name=\"indiciaMetadata\"" + style: form + indiciaImage: + contentType: "application/octet-stream, application/pdf, image/jpg, image/png, image/gif, image/tiff, image/svg+xml" + headers: + Content-Disposition: + description: Additional information on how the client should handle the content. + style: simple + explode: false + schema: + type: string + example: "Content-Disposition: attachment; filename=\"indicia-imb-image.jpg\"" + style: form + receiptImage: + contentType: "application/octet-stream, application/pdf, image/jpg, image/png, image/gif, image/tiff, image/svg+xml" + headers: + Content-Disposition: + description: Additional information on how the client should handle the content. + style: simple + explode: false + schema: + type: string + example: "Content-Disposition: attachment; filename=\"receipt-imb-image.jpg\"" + style: form + examples: + style: form + application/json: + schema: + $ref: '#/components/schemas/IndiciaImbResponse' + encoding: + metadata: + contentType: application/json + style: form + examples: + style: form + application/xml: + schema: + $ref: '#/components/schemas/IndiciaImbResponse' + encoding: + metadata: + contentType: application/xml + style: form + examples: + style: form + application/vnd.usps.labels+json: + schema: + $ref: '#/components/schemas/IndiciaImbResponse' + examples: + IndiciaIMBExampleResponse: + $ref: '#/components/examples/IndiciaIMBExampleResponse' + application/vnd.usps.labels+xml: + schema: + $ref: '#/components/schemas/IndiciaImbResponse' + "400": + description: Bad Request. There is an error in the received request. + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorMessage' + application/xml: + schema: + $ref: '#/components/schemas/ErrorMessage' + "401": + description: Unauthorized request. + headers: + WWW-Authenticate: + $ref: '#/components/headers/WWWAuthenticate' + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorMessage' + application/xml: + schema: + $ref: '#/components/schemas/ErrorMessage' + "403": + description: Access is denied. + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorMessage' + application/xml: + schema: + $ref: '#/components/schemas/ErrorMessage' + "429": + description: Too Many Requests. Too many requests have been received from the client in a short amount of time. + headers: + Retry-After: + $ref: '#/components/headers/RetryAfter' + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorMessage' + application/xml: + schema: + $ref: '#/components/schemas/ErrorMessage' + "503": + description: Service is unavailable. + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorMessage' + application/xml: + schema: + $ref: '#/components/schemas/ErrorMessage' + default: + description: Other unanticipated errors that may occur. + content: { } + security: + - OAuth: + - labels + /indicia/imb/{imb}: + delete: + tags: + - Resources + summary: Cancel a previously requested Intelligent Mail Barcode (IMB) label for First-Class letters, flats, and cards. + description: | + Cancel an Intelligent Mail Barcode (IMB) label for First-Class letters, flats, and cards or submit a refund request by providing the IMB. + + An IMB can be canceled if it has not yet been manifested. If it has been manifested, a refund request will be submitted for the unused IMB. + + Indicia without an IMB cannot be canceled. + + Note: + - You can only request a refund of an IMB once. + operationId: delete-indicia-imb + parameters: + - $ref: "#/components/parameters/IMB" + - $ref: "#/components/parameters/X-Payment-Authorization-Token" + responses: + "200": + description: Successful Operation + content: + application/json: + schema: + $ref: '#/components/schemas/IMBCancelResponse' + examples: + Label Canceled: + $ref: '#/components/examples/ExampleIMBCancel_CANCELED' + Refund Submitted: + $ref: '#/components/examples/ExampleIMBCancel_DISPUTED' + application/xml: + schema: + $ref: '#/components/schemas/IMBCancelResponse' + "400": + description: Bad Request. There is an error in the received request. + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorMessage' + application/xml: + schema: + $ref: '#/components/schemas/ErrorMessage' + "401": + description: Unauthorized request. + headers: + WWW-Authenticate: + $ref: '#/components/headers/WWWAuthenticate' + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorMessage' + application/xml: + schema: + $ref: '#/components/schemas/ErrorMessage' + "403": + description: Access is denied. + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorMessage' + application/xml: + schema: + $ref: '#/components/schemas/ErrorMessage' + "429": + description: Too Many Requests. Too many requests have been received from the client in a short amount of time. + headers: + Retry-After: + $ref: '#/components/headers/RetryAfter' + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorMessage' + application/xml: + schema: + $ref: '#/components/schemas/ErrorMessage' + "503": + description: Service is unavailable. + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorMessage' + application/xml: + schema: + $ref: '#/components/schemas/ErrorMessage' + default: + description: Other unanticipated errors that may occur. + content: { } + security: + - OAuth: + - labels + /branding: + post: + tags: + - Resources + summary: Upload an SVG image to be used as a label branding image. + description: | + The USPS Logo Branding endpoint allows you to upload your custom image or + logo to USPS APIs. A UUID is generated for your custom image to + leverage for outbound Domestic Labels and Return Labels requests. The + user will indicate how their custom image or multiple images will + display, and the option to specify the shape of their image when + generating labels and return labels. + + Note: + * It may take up to 15 minutes for the label image to propagate to all regions for label usage. + * There is a limit of 100 images to be stored per customer reference ID (CRID). The CRID will be taken from the `LABEL_OWNER` role of your Payment Authorization token. + * The root svg element requires a `viewbox` attribute + * The displayed aspect ratio of images on a label is 1:1 for square images and 33:5 for rectangle images. + * Recommendation: Allow the artwork inside the viewbox to occupy 100% of the space. This will allow your logo to be shown at the highest resolution possible. + * `