{
  "openapi": "3.0.0",
  "paths": {
    "/api/v1/customer/me": {
      "get": {
        "operationId": "CustomerPortalController_getMe_v1",
        "parameters": [],
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "ApiKey": []
          }
        ],
        "summary": "Get my profile and linked customer account",
        "tags": [
          "Account & users"
        ]
      },
      "patch": {
        "operationId": "CustomerPortalController_updateProfile_v1",
        "parameters": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UpdateProfileDto"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "ApiKey": []
          }
        ],
        "summary": "Update my profile (name, phone)",
        "tags": [
          "Account & users"
        ]
      }
    },
    "/api/v1/customer/billing": {
      "patch": {
        "operationId": "CustomerPortalController_updateBillingDetails_v1",
        "parameters": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UpdateBillingDto"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "ApiKey": []
          }
        ],
        "summary": "Update billing details (address, VAT, billing contact) on the linked customer account",
        "tags": [
          "Account & users"
        ]
      }
    },
    "/api/v1/customer/me/change-password": {
      "post": {
        "operationId": "CustomerPortalController_changePassword_v1",
        "parameters": [],
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "ApiKey": []
          }
        ],
        "summary": "Change my password",
        "tags": [
          "Account & users"
        ]
      }
    },
    "/api/v1/customer/shipments": {
      "get": {
        "operationId": "CustomerPortalController_getShipments_v1",
        "parameters": [
          {
            "name": "page",
            "required": false,
            "in": "query",
            "schema": {
              "type": "number"
            }
          },
          {
            "name": "limit",
            "required": false,
            "in": "query",
            "schema": {
              "type": "number"
            }
          },
          {
            "name": "search",
            "required": false,
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "dateFrom",
            "required": false,
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "dateTo",
            "required": false,
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "serviceLevel",
            "required": false,
            "in": "query",
            "schema": {
              "enum": [
                "SAME_DAY",
                "OVERNIGHT",
                "ECONOMY",
                "ECONOMY_REGIONAL",
                "BUDGET",
                "INTERNATIONAL"
              ],
              "type": "string"
            }
          },
          {
            "name": "status",
            "required": false,
            "in": "query",
            "schema": {
              "enum": [
                "DRAFT",
                "CREATED",
                "COLLECTION_ASSIGNED",
                "COLLECTED",
                "CHECKED_IN",
                "AT_HUB",
                "IN_TRANSIT",
                "AT_DESTINATION_HUB",
                "OUT_FOR_DELIVERY",
                "DELIVERED",
                "FAILED_DELIVERY",
                "RETURNED_TO_HUB",
                "REDELIVERY_SCHEDULED",
                "RETURNED_TO_SENDER",
                "LOST",
                "CANCELLED",
                "HELD"
              ],
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "ApiKey": []
          }
        ],
        "summary": "List my shipments",
        "tags": [
          "Shipments"
        ]
      }
    },
    "/api/v1/customer/shipments/status-summary": {
      "get": {
        "operationId": "CustomerPortalController_getShipmentStatusSummary_v1",
        "parameters": [],
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "ApiKey": []
          }
        ],
        "summary": "Get active shipment counts grouped by status",
        "tags": [
          "Shipments"
        ]
      }
    },
    "/api/v1/customer/shipments/{id}": {
      "get": {
        "operationId": "CustomerPortalController_getShipment_v1",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "ApiKey": []
          }
        ],
        "summary": "Get shipment detail + tracking timeline",
        "tags": [
          "Shipments"
        ]
      }
    },
    "/api/v1/customer/shipments/{id}/pod": {
      "get": {
        "operationId": "CustomerPortalController_getPod_v1",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "ApiKey": []
          }
        ],
        "summary": "Get POD signature URL for a delivered shipment",
        "tags": [
          "Shipments"
        ]
      }
    },
    "/api/v1/customer/shipments/{id}/label": {
      "get": {
        "operationId": "CustomerPortalController_getLabel_v1",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "ApiKey": []
          }
        ],
        "summary": "Download waybill label PDF (100×150 mm)",
        "tags": [
          "Shipments"
        ]
      }
    },
    "/api/v1/customer/shipments/{id}/cancel": {
      "post": {
        "operationId": "CustomerPortalController_cancelShipment_v1",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "ApiKey": []
          }
        ],
        "summary": "Cancel a shipment (only allowed before collection)",
        "tags": [
          "Shipments"
        ]
      }
    },
    "/api/v1/customer/wallet": {
      "get": {
        "operationId": "CustomerPortalController_walletBalance_v1",
        "parameters": [],
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "ApiKey": []
          }
        ],
        "summary": "My account balance, available funds and low-balance flag",
        "tags": [
          "Wallet & top-ups"
        ]
      }
    },
    "/api/v1/customer/wallet/ledger": {
      "get": {
        "operationId": "CustomerPortalController_walletLedger_v1",
        "parameters": [
          {
            "name": "page",
            "required": false,
            "in": "query",
            "schema": {
              "type": "number"
            }
          },
          {
            "name": "limit",
            "required": false,
            "in": "query",
            "schema": {
              "type": "number"
            }
          }
        ],
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "ApiKey": []
          }
        ],
        "summary": "My account statement — every top-up and charge, newest first",
        "tags": [
          "Wallet & top-ups"
        ]
      }
    },
    "/api/v1/customer/wallet/topup": {
      "post": {
        "description": "The balance is credited when PayFast confirms the payment, not when the redirect happens.",
        "operationId": "CustomerPortalController_walletTopUp_v1",
        "parameters": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/WalletTopUpDto"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "ApiKey": []
          }
        ],
        "summary": "Start a PayFast top-up — returns the redirect URL",
        "tags": [
          "Wallet & top-ups"
        ]
      }
    },
    "/api/v1/customer/quote": {
      "post": {
        "operationId": "CustomerPortalController_getQuotes_v1",
        "parameters": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CustomerQuoteDto"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "ApiKey": []
          }
        ],
        "summary": "Get shipping quotes for all service levels",
        "tags": [
          "Quotes & booking"
        ]
      }
    },
    "/api/v1/customer/book": {
      "post": {
        "operationId": "CustomerPortalController_createBooking_v1",
        "parameters": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CustomerBookDto"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": ""
          }
        },
        "security": [
          {
            "ApiKey": []
          }
        ],
        "summary": "Submit a booking request",
        "tags": [
          "Quotes & booking"
        ]
      }
    },
    "/api/v1/customer/bookings": {
      "get": {
        "operationId": "CustomerPortalController_getBookings_v1",
        "parameters": [
          {
            "name": "page",
            "required": false,
            "in": "query",
            "schema": {
              "type": "number"
            }
          },
          {
            "name": "limit",
            "required": false,
            "in": "query",
            "schema": {
              "type": "number"
            }
          }
        ],
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "ApiKey": []
          }
        ],
        "summary": "List my booking requests",
        "tags": [
          "Quotes & booking"
        ]
      }
    },
    "/api/v1/customer/invoices": {
      "get": {
        "operationId": "CustomerPortalController_getInvoices_v1",
        "parameters": [
          {
            "name": "page",
            "required": false,
            "in": "query",
            "schema": {
              "type": "number"
            }
          },
          {
            "name": "limit",
            "required": false,
            "in": "query",
            "schema": {
              "type": "number"
            }
          },
          {
            "name": "search",
            "required": false,
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "status",
            "required": false,
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "dateFrom",
            "required": false,
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "dateTo",
            "required": false,
            "in": "query",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "ApiKey": []
          }
        ],
        "summary": "List my invoices",
        "tags": [
          "Invoices & statements"
        ]
      }
    },
    "/api/v1/customer/invoices/{id}": {
      "get": {
        "operationId": "CustomerPortalController_getInvoice_v1",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "ApiKey": []
          }
        ],
        "summary": "Get invoice detail",
        "tags": [
          "Invoices & statements"
        ]
      }
    },
    "/api/v1/customer/invoices/{id}/pdf": {
      "get": {
        "operationId": "CustomerPortalController_downloadInvoicePdf_v1",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "ApiKey": []
          }
        ],
        "summary": "Download invoice PDF",
        "tags": [
          "Invoices & statements"
        ]
      }
    },
    "/api/v1/customer/bookings/{referenceNumber}/pay": {
      "post": {
        "operationId": "CustomerPortalController_initiateBookingPayment_v1",
        "parameters": [
          {
            "name": "referenceNumber",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "ApiKey": []
          }
        ],
        "summary": "Initiate PayFast payment for a prepaid booking — returns redirect URL",
        "tags": [
          "Quotes & booking"
        ]
      }
    },
    "/api/v1/customer/invoices/{id}/pay": {
      "post": {
        "operationId": "CustomerPortalController_initiateInvoicePayment_v1",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "ApiKey": []
          }
        ],
        "summary": "Initiate PayFast payment for an invoice — returns redirect URL",
        "tags": [
          "Invoices & statements"
        ]
      }
    },
    "/api/v1/customer/credit-notes/{id}": {
      "get": {
        "operationId": "CustomerPortalController_getCreditNote_v1",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "ApiKey": []
          }
        ],
        "summary": "Get credit note detail",
        "tags": [
          "Invoices & statements"
        ]
      }
    },
    "/api/v1/customer/statement": {
      "get": {
        "operationId": "CustomerPortalController_getStatement_v1",
        "parameters": [],
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "ApiKey": []
          }
        ],
        "summary": "Get account statement with age analysis",
        "tags": [
          "Invoices & statements"
        ]
      }
    },
    "/api/v1/customer/statement/pay": {
      "post": {
        "operationId": "CustomerPortalController_payStatementBalance_v1",
        "parameters": [],
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "ApiKey": []
          }
        ],
        "summary": "Initiate PayFast payment for the total outstanding statement balance",
        "tags": [
          "Invoices & statements"
        ]
      }
    },
    "/api/v1/customer/statement/pdf": {
      "get": {
        "operationId": "CustomerPortalController_downloadStatementPdf_v1",
        "parameters": [],
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "ApiKey": []
          }
        ],
        "summary": "Download account statement as PDF",
        "tags": [
          "Invoices & statements"
        ]
      }
    },
    "/api/v1/customer/addresses": {
      "get": {
        "operationId": "CustomerPortalController_getAddresses_v1",
        "parameters": [],
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "ApiKey": []
          }
        ],
        "summary": "List my saved addresses",
        "tags": [
          "Address book"
        ]
      },
      "post": {
        "operationId": "CustomerPortalController_createAddress_v1",
        "parameters": [],
        "responses": {
          "201": {
            "description": ""
          }
        },
        "security": [
          {
            "ApiKey": []
          }
        ],
        "summary": "Add a saved address",
        "tags": [
          "Address book"
        ]
      }
    },
    "/api/v1/customer/addresses/{id}": {
      "patch": {
        "operationId": "CustomerPortalController_updateAddress_v1",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "ApiKey": []
          }
        ],
        "summary": "Update a saved address",
        "tags": [
          "Address book"
        ]
      },
      "delete": {
        "operationId": "CustomerPortalController_deleteAddress_v1",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "ApiKey": []
          }
        ],
        "summary": "Delete a saved address",
        "tags": [
          "Address book"
        ]
      }
    },
    "/api/v1/customer/api-keys": {
      "get": {
        "operationId": "CustomerPortalController_listApiKeys_v1",
        "parameters": [],
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "ApiKey": []
          }
        ],
        "summary": "List my API keys (masked)",
        "tags": [
          "API keys & webhooks"
        ]
      },
      "post": {
        "operationId": "CustomerPortalController_createApiKey_v1",
        "parameters": [],
        "responses": {
          "201": {
            "description": ""
          }
        },
        "security": [
          {
            "ApiKey": []
          }
        ],
        "summary": "Create an API key — returns raw key once",
        "tags": [
          "API keys & webhooks"
        ]
      }
    },
    "/api/v1/customer/api-keys/{id}": {
      "delete": {
        "operationId": "CustomerPortalController_revokeApiKey_v1",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "ApiKey": []
          }
        ],
        "summary": "Revoke an API key",
        "tags": [
          "API keys & webhooks"
        ]
      }
    },
    "/api/v1/customer/webhooks": {
      "get": {
        "operationId": "CustomerPortalController_listWebhooks_v1",
        "parameters": [],
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "ApiKey": []
          }
        ],
        "summary": "List my webhooks",
        "tags": [
          "API keys & webhooks"
        ]
      },
      "post": {
        "operationId": "CustomerPortalController_createWebhook_v1",
        "parameters": [],
        "responses": {
          "201": {
            "description": ""
          }
        },
        "security": [
          {
            "ApiKey": []
          }
        ],
        "summary": "Create a webhook",
        "tags": [
          "API keys & webhooks"
        ]
      }
    },
    "/api/v1/customer/webhooks/{id}": {
      "patch": {
        "operationId": "CustomerPortalController_updateWebhook_v1",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "ApiKey": []
          }
        ],
        "summary": "Update a webhook (url, events, isActive)",
        "tags": [
          "API keys & webhooks"
        ]
      },
      "delete": {
        "operationId": "CustomerPortalController_deleteWebhook_v1",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "ApiKey": []
          }
        ],
        "summary": "Delete a webhook",
        "tags": [
          "API keys & webhooks"
        ]
      }
    },
    "/api/v1/customer/webhooks/{id}/deliveries": {
      "get": {
        "operationId": "CustomerPortalController_getWebhookDeliveries_v1",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "ApiKey": []
          }
        ],
        "summary": "Get last 100 delivery attempts for a webhook",
        "tags": [
          "API keys & webhooks"
        ]
      }
    },
    "/api/v1/customer/webhooks/{id}/test": {
      "post": {
        "operationId": "CustomerPortalController_sendTestEvent_v1",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "ApiKey": []
          }
        ],
        "summary": "Send a test event to a webhook",
        "tags": [
          "API keys & webhooks"
        ]
      }
    },
    "/api/v1/customer/settings/general": {
      "get": {
        "operationId": "CustomerPortalController_getPortalSettings_v1",
        "parameters": [],
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "ApiKey": []
          }
        ],
        "summary": "Get portal general settings + logo URL",
        "tags": [
          "Portal settings"
        ]
      },
      "patch": {
        "operationId": "CustomerPortalController_updatePortalSettings_v1",
        "parameters": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UpdatePortalSettingsDto"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "ApiKey": []
          }
        ],
        "summary": "Update portal general settings (toggles + custom label)",
        "tags": [
          "Portal settings"
        ]
      }
    },
    "/api/v1/customer/settings/general/logo": {
      "post": {
        "operationId": "CustomerPortalController_uploadPortalLogo_v1",
        "parameters": [],
        "requestBody": {
          "required": true,
          "content": {
            "multipart/form-data": {
              "schema": {
                "type": "object",
                "properties": {
                  "file": {
                    "type": "string",
                    "format": "binary"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": ""
          }
        },
        "security": [
          {
            "ApiKey": []
          }
        ],
        "summary": "Upload company logo (PNG/JPG/WebP, max 2 MB)",
        "tags": [
          "Portal settings"
        ]
      },
      "delete": {
        "operationId": "CustomerPortalController_deletePortalLogo_v1",
        "parameters": [],
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "ApiKey": []
          }
        ],
        "summary": "Delete company logo",
        "tags": [
          "Portal settings"
        ]
      }
    },
    "/api/v1/customer/settings/shipments": {
      "get": {
        "operationId": "CustomerPortalController_getShipmentSettings_v1",
        "parameters": [],
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "ApiKey": []
          }
        ],
        "summary": "Get portal shipment settings",
        "tags": [
          "Portal settings"
        ]
      },
      "patch": {
        "operationId": "CustomerPortalController_updateShipmentSettings_v1",
        "parameters": [],
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "ApiKey": []
          }
        ],
        "summary": "Update portal shipment settings",
        "tags": [
          "Portal settings"
        ]
      }
    },
    "/api/v1/customer/notification-preferences": {
      "get": {
        "operationId": "CustomerPortalController_getNotificationPreferences_v1",
        "parameters": [],
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "ApiKey": []
          }
        ],
        "summary": "Get my notification preferences",
        "tags": [
          "Portal settings"
        ]
      },
      "put": {
        "operationId": "CustomerPortalController_updateNotificationPreferences_v1",
        "parameters": [],
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "ApiKey": []
          }
        ],
        "summary": "Update my notification preferences",
        "tags": [
          "Portal settings"
        ]
      }
    },
    "/api/v1/customer/analytics/dashboard": {
      "get": {
        "operationId": "CustomerPortalController_getAnalytics_v1",
        "parameters": [
          {
            "name": "from",
            "required": false,
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "to",
            "required": false,
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "serviceLevel",
            "required": false,
            "in": "query",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "ApiKey": []
          }
        ],
        "summary": "Get dashboard analytics (shipments chart + KPIs)",
        "tags": [
          "Account & users"
        ]
      }
    },
    "/api/v1/customer/account/users": {
      "get": {
        "operationId": "CustomerPortalController_listAccountUsers_v1",
        "parameters": [],
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "ApiKey": []
          }
        ],
        "summary": "List all portal users on this account",
        "tags": [
          "Account & users"
        ]
      }
    },
    "/api/v1/customer/account/users/invite": {
      "post": {
        "operationId": "CustomerPortalController_inviteUser_v1",
        "parameters": [],
        "responses": {
          "201": {
            "description": ""
          }
        },
        "security": [
          {
            "ApiKey": []
          }
        ],
        "summary": "Send a portal invite to a new user",
        "tags": [
          "Account & users"
        ]
      }
    },
    "/api/v1/customer/account/users/invites": {
      "get": {
        "operationId": "CustomerPortalController_listPendingInvites_v1",
        "parameters": [],
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "ApiKey": []
          }
        ],
        "summary": "List pending invites for this account",
        "tags": [
          "Account & users"
        ]
      }
    },
    "/api/v1/customer/account/users/invites/{id}": {
      "delete": {
        "operationId": "CustomerPortalController_revokeInvite_v1",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "ApiKey": []
          }
        ],
        "summary": "Revoke a pending invite",
        "tags": [
          "Account & users"
        ]
      }
    },
    "/api/v1/customer/account/users/{userId}": {
      "patch": {
        "operationId": "CustomerPortalController_updateAccountUser_v1",
        "parameters": [
          {
            "name": "userId",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "ApiKey": []
          }
        ],
        "summary": "Update a portal user role, permissions or cost centre",
        "tags": [
          "Account & users"
        ]
      },
      "delete": {
        "operationId": "CustomerPortalController_removeAccountUser_v1",
        "parameters": [
          {
            "name": "userId",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "ApiKey": []
          }
        ],
        "summary": "Remove a portal user from this account",
        "tags": [
          "Account & users"
        ]
      }
    },
    "/api/v1/customer/account/cost-centres": {
      "get": {
        "operationId": "CustomerPortalController_listCostCentres_v1",
        "parameters": [],
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "ApiKey": []
          }
        ],
        "summary": "List cost centres for this account",
        "tags": [
          "Account & users"
        ]
      },
      "post": {
        "operationId": "CustomerPortalController_createCostCentre_v1",
        "parameters": [],
        "responses": {
          "201": {
            "description": ""
          }
        },
        "security": [
          {
            "ApiKey": []
          }
        ],
        "summary": "Create a cost centre",
        "tags": [
          "Account & users"
        ]
      }
    },
    "/api/v1/customer/account/cost-centres/{id}": {
      "patch": {
        "operationId": "CustomerPortalController_updateCostCentre_v1",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "ApiKey": []
          }
        ],
        "summary": "Update a cost centre",
        "tags": [
          "Account & users"
        ]
      },
      "delete": {
        "operationId": "CustomerPortalController_deleteCostCentre_v1",
        "parameters": [
          {
            "name": "id",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": ""
          }
        },
        "security": [
          {
            "ApiKey": []
          }
        ],
        "summary": "Delete or deactivate a cost centre",
        "tags": [
          "Account & users"
        ]
      }
    },
    "/api/v1/public/{tenantSlug}/config": {
      "get": {
        "operationId": "PublicCustomerConfigController_getConfig_v1",
        "parameters": [
          {
            "name": "tenantSlug",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": ""
          }
        },
        "summary": "Get tenant branding config for the customer portal login screen",
        "tags": [
          "Public (no key needed)"
        ],
        "security": []
      }
    },
    "/api/v1/customer/invite/{token}": {
      "get": {
        "operationId": "CustomerInviteController_getInviteDetails_v1",
        "parameters": [
          {
            "name": "token",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": ""
          }
        },
        "summary": "Get invite details (public — for the accept invite page)",
        "tags": [
          "Public (no key needed)"
        ],
        "security": []
      }
    },
    "/api/v1/customer/invite/{token}/accept": {
      "post": {
        "operationId": "CustomerInviteController_acceptInvite_v1",
        "parameters": [
          {
            "name": "token",
            "required": true,
            "in": "path",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": ""
          }
        },
        "summary": "Accept an invite and create an account",
        "tags": [
          "Public (no key needed)"
        ],
        "security": []
      }
    }
  },
  "info": {
    "title": "ParcelOps Customer API",
    "version": "1.0.0",
    "contact": {
      "name": "ParcelOps",
      "url": "",
      "email": "api@parcelops.co.za"
    },
    "description": "The ParcelOps customer integration API: quote, book, track and pull documents for your own shipments, plus webhooks for live status pushes. Base URL: `https://api.parcelops.co.za/api/v1`.\n\n# Authentication\n\nAuthenticate every request with an API key sent as a Bearer token:\n\n```\nAuthorization: Bearer po_your_api_key\n```\n\nGenerate keys under **Integrations → API Keys** in your customer portal. A key acts as the customer account it belongs to — it sees that account's shipments, invoices and documents, and nothing else. Keys can be revoked there at any time.\n\n# Errors\n\nStandard HTTP status codes with a JSON body. `400` carries a descriptive validation message, `401` means a missing or revoked key, `403` means the key's account may not see that resource, `404` an unknown resource. Timestamps are ISO 8601 (`YYYY-MM-DDTHH:mm:ssZ`).\n\n# Shipment statuses\n\nA shipment's `status` walks this lifecycle:\n\n| Status | Meaning |\n|---|---|\n| `CREATED` | Booked, not yet collected |\n| `COLLECTION_ASSIGNED` | A driver is assigned to collect |\n| `COLLECTED` | Picked up from the sender |\n| `CHECKED_IN` / `AT_HUB` | Received and scanned at a hub |\n| `IN_TRANSIT` | Moving between hubs on a manifest |\n| `AT_DESTINATION_HUB` | At the hub nearest the recipient |\n| `OUT_FOR_DELIVERY` | On a vehicle for final delivery |\n| `DELIVERED` | Delivered, POD captured |\n| `FAILED_DELIVERY` | An attempt failed (reason recorded) |\n| `REDELIVERY_SCHEDULED` | A new attempt is scheduled |\n| `RETURNED_TO_HUB` / `RETURNED_TO_SENDER` | Coming back |\n| `HELD` / `LOST` / `CANCELLED` | Exceptions |\n\n# Webhooks\n\nSubscribe under **Integrations → Webhooks** in the portal and ParcelOps POSTs events to your endpoint as they happen: `shipment.collected`, `shipment.out_for_delivery`, `shipment.delivered`, `shipment.failed_delivery`, `invoice.issued`, `invoice.paid`.\n\nEvery delivery is signed — `X-Parcelops-Signature` is `sha256=<HMAC-SHA256 of the raw body>` keyed with your endpoint secret — and carries a unique `X-Parcelops-Delivery-Id` for deduplication. Respond 2xx within 15 seconds; failures retry five times with exponential backoff starting at one minute."
  },
  "tags": [
    {
      "name": "Shipments",
      "description": "Track, cancel, and pull labels and PODs for your shipments"
    },
    {
      "name": "Quotes & booking",
      "description": "Price a consignment and book it"
    },
    {
      "name": "Wallet & top-ups",
      "description": "Prepaid balance, ledger and PayFast top-ups"
    },
    {
      "name": "Invoices & statements",
      "description": "Documents, PDFs and online payment"
    },
    {
      "name": "Address book",
      "description": "Saved senders and recipients"
    },
    {
      "name": "Account & users",
      "description": "Your profile, billing details, users and cost centres"
    },
    {
      "name": "API keys & webhooks",
      "description": "Manage integration credentials and event subscriptions"
    },
    {
      "name": "Portal settings",
      "description": "Branding, shipment defaults and notification preferences"
    },
    {
      "name": "Public (no key needed)",
      "description": "Unauthenticated tenant branding and invite endpoints"
    }
  ],
  "servers": [
    {
      "url": "https://api.parcelops.co.za/api/v1",
      "description": "Production"
    }
  ],
  "components": {
    "securitySchemes": {
      "ApiKey": {
        "scheme": "bearer",
        "bearerFormat": "po_<apiKey>",
        "type": "http",
        "description": "Customer portal API key (prefix: po_). Generate via Integrations → API Keys in the customer portal."
      }
    },
    "schemas": {
      "UpdateProfileDto": {
        "type": "object",
        "properties": {
          "firstName": {
            "type": "string",
            "example": "Maria"
          },
          "lastName": {
            "type": "string",
            "example": "Jacobs"
          },
          "phone": {
            "type": "string",
            "example": "0821234567"
          }
        }
      },
      "UpdateBillingDto": {
        "type": "object",
        "properties": {
          "companyName": {
            "type": "string",
            "example": "Linx Xpress (Pty) Ltd"
          },
          "email": {
            "type": "string",
            "example": "accounts@linx.co.za"
          },
          "phone": {
            "type": "string",
            "example": "0441234567"
          },
          "vatNumber": {
            "type": "string",
            "example": "4123456789"
          },
          "billingAddress": {
            "type": "string",
            "example": "12 Main Road"
          },
          "billingCity": {
            "type": "string",
            "example": "Knysna"
          },
          "billingProvince": {
            "type": "string",
            "example": "Western Cape"
          },
          "billingPostal": {
            "type": "string",
            "example": "6570"
          }
        }
      },
      "WalletTopUpDto": {
        "type": "object",
        "properties": {
          "amount": {
            "type": "number",
            "example": 500,
            "description": "Amount in rand, two decimals max"
          },
          "returnUrl": {
            "type": "string",
            "example": "https://yourshop.co.za/wallet/success"
          },
          "cancelUrl": {
            "type": "string",
            "example": "https://yourshop.co.za/wallet/cancelled"
          }
        },
        "required": [
          "amount",
          "returnUrl",
          "cancelUrl"
        ]
      },
      "QuotePieceDto": {
        "type": "object",
        "properties": {
          "actualWeightKg": {
            "type": "number",
            "example": 4.5,
            "description": "Scale weight in kilograms"
          },
          "lengthCm": {
            "type": "number",
            "example": 40
          },
          "widthCm": {
            "type": "number",
            "example": 30
          },
          "heightCm": {
            "type": "number",
            "example": 25
          },
          "declaredValue": {
            "type": "number",
            "example": 1500,
            "description": "Declared value in rand, for insurance"
          }
        },
        "required": [
          "actualWeightKg"
        ]
      },
      "CustomerQuoteDto": {
        "type": "object",
        "properties": {
          "originSuburb": {
            "type": "string",
            "example": "Sandton"
          },
          "originPostalCode": {
            "type": "string",
            "example": "2196"
          },
          "destSuburb": {
            "type": "string",
            "example": "Sea Point"
          },
          "destPostalCode": {
            "type": "string",
            "example": "8005"
          },
          "pieces": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/QuotePieceDto"
            }
          }
        },
        "required": [
          "originSuburb",
          "originPostalCode",
          "destSuburb",
          "destPostalCode",
          "pieces"
        ]
      },
      "CustomerBookDto": {
        "type": "object",
        "properties": {
          "serviceLevel": {
            "type": "string",
            "enum": [
              "SAME_DAY",
              "OVERNIGHT",
              "ECONOMY",
              "ECONOMY_REGIONAL",
              "BUDGET",
              "INTERNATIONAL"
            ],
            "example": "OVERNIGHT"
          },
          "senderName": {
            "type": "string",
            "example": "Maria Jacobs"
          },
          "senderCompany": {
            "type": "string",
            "example": "Linx Xpress"
          },
          "senderStreet": {
            "type": "string",
            "example": "12 Main Road"
          },
          "senderComplex": {
            "type": "string",
            "example": "Unit 4, Harbour Park"
          },
          "senderSuburb": {
            "type": "string",
            "example": "Knysna Central"
          },
          "senderCity": {
            "type": "string",
            "example": "Knysna"
          },
          "senderProvince": {
            "type": "string",
            "example": "Western Cape"
          },
          "senderPostalCode": {
            "type": "string",
            "example": "6570"
          },
          "senderPhone": {
            "type": "string",
            "example": "0821234567"
          },
          "senderEmail": {
            "type": "string",
            "example": "maria@linx.co.za"
          },
          "recipientName": {
            "type": "string",
            "example": "Thandi Nkosi"
          },
          "recipientCompany": {
            "type": "string"
          },
          "recipientStreet": {
            "type": "string",
            "example": "8 Beach Road"
          },
          "recipientComplex": {
            "type": "string"
          },
          "recipientSuburb": {
            "type": "string",
            "example": "Sea Point"
          },
          "recipientCity": {
            "type": "string",
            "example": "Cape Town"
          },
          "recipientProvince": {
            "type": "string",
            "example": "Western Cape"
          },
          "recipientPostalCode": {
            "type": "string",
            "example": "8005"
          },
          "recipientPhone": {
            "type": "string",
            "example": "0837654321"
          },
          "recipientEmail": {
            "type": "string",
            "example": "thandi@example.co.za"
          },
          "pieces": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/QuotePieceDto"
            }
          },
          "specialInstructions": {
            "type": "string",
            "example": "Ring the buzzer at the gate"
          },
          "customerReference": {
            "type": "string",
            "example": "PO-2026-0042",
            "description": "Your own reference, shown on documents"
          },
          "quotedCharge": {
            "type": "number",
            "example": 245.5,
            "description": "The quoted charge you accepted, for price-lock validation"
          }
        },
        "required": [
          "serviceLevel",
          "senderName",
          "senderStreet",
          "senderSuburb",
          "senderCity",
          "senderProvince",
          "senderPostalCode",
          "senderPhone",
          "recipientName",
          "recipientStreet",
          "recipientSuburb",
          "recipientCity",
          "recipientProvince",
          "recipientPostalCode",
          "recipientPhone",
          "pieces"
        ]
      },
      "UpdatePortalSettingsDto": {
        "type": "object",
        "properties": {
          "requireShipmentApproval": {
            "type": "boolean",
            "description": "New shipments wait for an account admin to approve them"
          },
          "autoAddAddresses": {
            "type": "boolean",
            "description": "Save new sender/recipient addresses to the address book automatically"
          },
          "hideCollectionDetails": {
            "type": "boolean",
            "description": "Hide collection details from non-admin portal users"
          },
          "autoRemoveTrackingInfo": {
            "type": "boolean",
            "description": "Strip tracking info from delivered-shipment lists"
          },
          "customTrackingRefLabel": {
            "type": "string",
            "example": "Order number"
          }
        }
      }
    }
  },
  "security": [
    {
      "ApiKey": []
    }
  ]
}