Flows

Boxy Delivery Flow

Boxy delivery provider integration flow.

Boxy Delivery Provider Flow

Boxy is an external last-mile delivery provider integrated under Sendy's delivery-provider abstraction. Stores link their Boxy merchant account, then use the api/v1/store/boxy/* surface to manage orders, pick-ups, pick-up locations, and regions — all proxied to the Boxy REST API.

1) Architecture Overview

Store JWT request
      │
      ▼
StoreBoxyController          api/v1/store/boxy/*
      │
      ▼
IBoxyStoreService            resolves store's Boxy credentials
      │
      ▼
IBoxyClient (BoxyClient)     typed HttpClient → Boxy REST API
  • Credential storage: StoreDeliveryProviderLinkUsernameEncrypted = api_key, PasswordEncrypted = api_secret. TokenEncrypted/TokenFetchedAt are unused (Boxy uses static key+secret, no session token).
  • Dispatch discriminator: DeliveryCompany.Slug == "boxy". StoreDeliveryProviderService checks this slug when pushing Sendy orders to an external provider.
  • Base URL: https://api.tryboxy.com/api/v1/ (production) or https://api-pre.tryboxy.dev/api/v1/ (sandbox). Controlled by Boxy:Sandbox in appsettings.json.
  • Auth headers: every Boxy request carries api-key: <key> and api-secret: <secret>.
  • Serialization: snake_case (JsonNamingPolicy.SnakeCaseLower) for all Boxy request bodies.

2) Linking a Boxy Account

Stores link/unlink delivery providers via the shared delivery-provider endpoints (not Boxy-specific):

POST   api/v1/store/delivery-providers/links
DELETE api/v1/store/delivery-providers/links/{linkId}
GET    api/v1/store/delivery-providers/links

Request body for linking:

{
  "deliveryCompanySlug": "boxy",
  "username": "<api_key>",
  "password": "<api_secret>"
}

On link, StoreDeliveryProviderService.LinkProviderAsync calls IBoxyClient.ValidateCredentialsAsync (GET /user) to confirm the credentials before saving. Credentials are AES-encrypted at rest via IEncryptionService.

Permission required: store.delivery_providers.link

3) Address Mapping (Admin)

Boxy uses text-based province codes and region names (unlike Al-Waseet's numeric IDs). Admins map Sendy address entities to Boxy equivalents:

PUT api/v1/admin/delivery-providers/boxy/address-mapping/provinces/{code}
    body: { "boxyProvinceCode": "BGH" }

PUT api/v1/admin/delivery-providers/boxy/address-mapping/areas/{areaId}
    body: { "boxyRegionName": "Al-Karrada" }

Stored on:

  • AddressProvince.BoxyProvinceCode (max 64 chars)
  • AddressArea.BoxyRegionName (max 256 chars)

Use GET api/v1/store/boxy/regions to fetch all Boxy provinces/regions and their UIDs, then map them to Sendy's address hierarchy.

4) Creating a Sendy Order with Boxy Fulfillment

The normal Sendy order creation flow (POST api/v1/store/integrations/orders or via the store UI) assigns a delivery company. When the assigned DC has Slug == "boxy", StoreDeliveryProviderService calls IBoxyClient.CreateOrderAsync and writes the result to OrderExternalDelivery:

FieldSource
ExternalOrderIdBoxy uid
ExternalTrackingLinkBoxy tracking_link
ExternalStatusLabelBoxy status slug (string)

Status slugs are constants in Sendy.Domain/Enums/Orders/BoxyOrderStatus.cs: pending, approved, picked_up, at_hub, on_the_way, delivered, failed_delivery, returned, cancelled, and others.

Status sync: POST api/v1/store/delivery-providers/orders/{orderId}/sync-status triggers IBoxyClient.GetOrderStatusAsync and updates ExternalStatusLabel.

5) Boxy-Direct Store API

All endpoints require JWT + [Authorize]. IBoxyStoreService.GetCredentialsAsync looks up the active StoreDeliveryProviderLink with Slug == "boxy" for the current store; if none is found, every endpoint returns HTTP 404 "No active Boxy account linked to this store."

Orders

MethodRoutePermissionDescription
GETboxy/ordersStoreDeliveryProvidersViewList all Boxy orders for this merchant
GETboxy/orders/by-custom-id/{customId}StoreDeliveryProvidersViewFetch order by the store's custom ID
GETboxy/orders/{orderUid}/labelStoreDeliveryProvidersViewDownload single order label (PDF)
GETboxy/orders/labels?orderUids=…StoreDeliveryProvidersViewBulk label download (PDF); orderUids repeatable query param
PATCHboxy/orders/{orderUid}StoreDeliveryProvidersLinkUpdate order details (same body as create)
DELETEboxy/orders/{orderUid}StoreDeliveryProvidersLinkDelete/void a Boxy order

Label endpoints return raw bytes with Content-Type from the Boxy response (typically application/pdf), not a JSON envelope.

Pick-ups

MethodRoutePermissionDescription
GETboxy/pick-upsViewList all pick-ups
GETboxy/pick-ups/{pickupUid}ViewGet single pick-up
GETboxy/pick-ups/{pickupUid}/labelsViewDownload pick-up labels (PDF)
POSTboxy/pick-upsLinkRequest a pick-up for a list of order UIDs
POSTboxy/pick-ups/bulkLinkBulk pick-up request; optional filterUids[] query param
POSTboxy/pick-ups/{pickupUid}/cancelLinkCancel a single pick-up
POSTboxy/pick-ups/bulk-cancelLinkBulk cancel; optional filterUids[] query param

Request body for POST boxy/pick-ups:

{ "orderUids": ["uid1", "uid2"] }

Pick-up Locations

MethodRoutePermissionDescription
GETboxy/pick-up-locationsViewList saved pick-up addresses
GETboxy/pick-up-locations/cash-dropoffViewGet the default cash drop-off location
GETboxy/pick-up-locations/{locationUid}ViewGet a single location
POSTboxy/pick-up-locationsLinkCreate a new pick-up location
PATCHboxy/pick-up-locations/{locationUid}LinkUpdate a pick-up location
DELETEboxy/pick-up-locations/{locationUid}LinkDelete a pick-up location

Request body for create/update:

{
  "fullName": "Main Warehouse",
  "type": "WAREHOUSE",
  "default": true,
  "regionUid": "<boxy-region-uid>",
  "title": "Baghdad Main",
  "phone": "07701234567",
  "addressText": "Al-Karrada, Street 14",
  "lng": "44.3661",
  "lat": "33.3152",
  "email": "[email protected]",
  "description": "Ground floor"
}

regionUid comes from GET boxy/regions.

Regions

MethodRoutePermissionDescription
GETboxy/regionsViewList all Boxy provinces/regions with their UIDs

Use this to discover regionUid values needed when creating pick-up locations, and to set up admin address mapping.

6) Response Contract

Most endpoints return the standard ApiResponse<object> envelope:

{
  "success": true,
  "message": "Orders fetched successfully",
  "data": { ... }
}

Label endpoints (/label, /labels, pick-ups/{uid}/labels) return the raw file bytes with the Boxy content-type header. On error they return:

{ "error": "..." }

Error cases:

ConditionHTTPMessage
No Boxy link for this store404"No active Boxy account linked to this store."
Boxy API returned no data502"Boxy API returned no data."
Entity not found on Boxy404"Order/Pick-up/Location not found."
Boxy operation failed502Boxy error message

7) Typical Setup Flow

  1. Admin: add Boxy as a delivery company with Slug = "boxy" via the admin panel.
  2. Admin: run GET boxy/regions (or check Boxy dashboard) and map provinces/areas via PUT api/v1/admin/delivery-providers/boxy/address-mapping/....
  3. Store owner: link their Boxy account via POST api/v1/store/delivery-providers/links with deliveryCompanySlug = "boxy", username = <api_key>, password = <api_secret>.
  4. Store owner: create a pick-up location via POST api/v1/store/boxy/pick-up-locations.
  5. Sendy order workflow: when a Sendy order is assigned to the Boxy DC, the system auto-pushes it to Boxy via CreateOrderAsync. The OrderExternalDelivery record tracks the Boxy UID, tracking link, and status label.
  6. Pick-up: store requests pick-up via POST api/v1/store/boxy/pick-ups with the relevant Boxy order UIDs.
  7. Labels: download shipping labels via GET api/v1/store/boxy/orders/{uid}/label or in bulk.

8) Configuration

// appsettings.json
{
  "Boxy": {
    "Sandbox": true
  }
}

Set Boxy:Sandbox to false in production to point at https://api.tryboxy.com/api/v1/.

9) Source References

LayerFile
ControllerSendy.Api/Controllers/V1/Store/StoreBoxyController.cs
Store service interfaceSendy.Application/Interfaces/Services/StoreArea/IBoxyStoreService.cs
Store service implementationSendy.Application/Services/StoreArea/BoxyStoreService.cs
HTTP client interfaceSendy.Application/Interfaces/Services/IBoxyClient.cs
HTTP client implementationSendy.Infrastructure/Services/BoxyClient.cs
Status constantsSendy.Domain/Enums/Orders/BoxyOrderStatus.cs
Create order request DTOSendy.Application/DTOs/Requests/Boxy/BoxyCreateOrderRequest.cs
Pick-up request DTOSendy.Application/DTOs/Requests/Boxy/BoxyPickupRequest.cs
Pick-up location request DTOSendy.Application/DTOs/Requests/Boxy/BoxyPickupLocationRequest.cs
Raw (PDF) result DTOSendy.Application/DTOs/Responses/Boxy/BoxyRawResult.cs
Province mappingSendy.Domain/Entities/Location/AddressProvince.csBoxyProvinceCode
Area mappingSendy.Domain/Entities/Location/AddressArea.csBoxyRegionName
Status columnSendy.Domain/Entities/Orders/OrderExternalDelivery.csExternalStatusLabel
Admin mapping endpointsSendy.Api/Controllers/V1/Admin/AdminDeliveryProvidersController.cs
DI registrationSendy.Api/Hosting/WebApplicationBuilderExtensions.cs
EF migrationSendy.Infrastructure/Migrations/20260605203917_AddBoxyProviderSupport.cs

Source: DOCS/flows/boxy-delivery-provider-flow.md