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:
StoreDeliveryProviderLink—UsernameEncrypted=api_key,PasswordEncrypted=api_secret.TokenEncrypted/TokenFetchedAtare unused (Boxy uses static key+secret, no session token). - Dispatch discriminator:
DeliveryCompany.Slug == "boxy".StoreDeliveryProviderServicechecks this slug when pushing Sendy orders to an external provider. - Base URL:
https://api.tryboxy.com/api/v1/(production) orhttps://api-pre.tryboxy.dev/api/v1/(sandbox). Controlled byBoxy:Sandboxinappsettings.json. - Auth headers: every Boxy request carries
api-key: <key>andapi-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:
| Field | Source |
|---|---|
ExternalOrderId | Boxy uid |
ExternalTrackingLink | Boxy tracking_link |
ExternalStatusLabel | Boxy 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
| Method | Route | Permission | Description |
|---|---|---|---|
GET | boxy/orders | StoreDeliveryProvidersView | List all Boxy orders for this merchant |
GET | boxy/orders/by-custom-id/{customId} | StoreDeliveryProvidersView | Fetch order by the store's custom ID |
GET | boxy/orders/{orderUid}/label | StoreDeliveryProvidersView | Download single order label (PDF) |
GET | boxy/orders/labels?orderUids=… | StoreDeliveryProvidersView | Bulk label download (PDF); orderUids repeatable query param |
PATCH | boxy/orders/{orderUid} | StoreDeliveryProvidersLink | Update order details (same body as create) |
DELETE | boxy/orders/{orderUid} | StoreDeliveryProvidersLink | Delete/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
| Method | Route | Permission | Description |
|---|---|---|---|
GET | boxy/pick-ups | View | List all pick-ups |
GET | boxy/pick-ups/{pickupUid} | View | Get single pick-up |
GET | boxy/pick-ups/{pickupUid}/labels | View | Download pick-up labels (PDF) |
POST | boxy/pick-ups | Link | Request a pick-up for a list of order UIDs |
POST | boxy/pick-ups/bulk | Link | Bulk pick-up request; optional filterUids[] query param |
POST | boxy/pick-ups/{pickupUid}/cancel | Link | Cancel a single pick-up |
POST | boxy/pick-ups/bulk-cancel | Link | Bulk cancel; optional filterUids[] query param |
Request body for POST boxy/pick-ups:
{ "orderUids": ["uid1", "uid2"] }
Pick-up Locations
| Method | Route | Permission | Description |
|---|---|---|---|
GET | boxy/pick-up-locations | View | List saved pick-up addresses |
GET | boxy/pick-up-locations/cash-dropoff | View | Get the default cash drop-off location |
GET | boxy/pick-up-locations/{locationUid} | View | Get a single location |
POST | boxy/pick-up-locations | Link | Create a new pick-up location |
PATCH | boxy/pick-up-locations/{locationUid} | Link | Update a pick-up location |
DELETE | boxy/pick-up-locations/{locationUid} | Link | Delete 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
| Method | Route | Permission | Description |
|---|---|---|---|
GET | boxy/regions | View | List 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:
| Condition | HTTP | Message |
|---|---|---|
| No Boxy link for this store | 404 | "No active Boxy account linked to this store." |
| Boxy API returned no data | 502 | "Boxy API returned no data." |
| Entity not found on Boxy | 404 | "Order/Pick-up/Location not found." |
| Boxy operation failed | 502 | Boxy error message |
7) Typical Setup Flow
- Admin: add Boxy as a delivery company with
Slug = "boxy"via the admin panel. - Admin: run
GET boxy/regions(or check Boxy dashboard) and map provinces/areas viaPUT api/v1/admin/delivery-providers/boxy/address-mapping/.... - Store owner: link their Boxy account via
POST api/v1/store/delivery-providers/linkswithdeliveryCompanySlug = "boxy",username = <api_key>,password = <api_secret>. - Store owner: create a pick-up location via
POST api/v1/store/boxy/pick-up-locations. - Sendy order workflow: when a Sendy order is assigned to the Boxy DC, the system auto-pushes it to Boxy via
CreateOrderAsync. TheOrderExternalDeliveryrecord tracks the Boxy UID, tracking link, and status label. - Pick-up: store requests pick-up via
POST api/v1/store/boxy/pick-upswith the relevant Boxy order UIDs. - Labels: download shipping labels via
GET api/v1/store/boxy/orders/{uid}/labelor 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
| Layer | File |
|---|---|
| Controller | Sendy.Api/Controllers/V1/Store/StoreBoxyController.cs |
| Store service interface | Sendy.Application/Interfaces/Services/StoreArea/IBoxyStoreService.cs |
| Store service implementation | Sendy.Application/Services/StoreArea/BoxyStoreService.cs |
| HTTP client interface | Sendy.Application/Interfaces/Services/IBoxyClient.cs |
| HTTP client implementation | Sendy.Infrastructure/Services/BoxyClient.cs |
| Status constants | Sendy.Domain/Enums/Orders/BoxyOrderStatus.cs |
| Create order request DTO | Sendy.Application/DTOs/Requests/Boxy/BoxyCreateOrderRequest.cs |
| Pick-up request DTO | Sendy.Application/DTOs/Requests/Boxy/BoxyPickupRequest.cs |
| Pick-up location request DTO | Sendy.Application/DTOs/Requests/Boxy/BoxyPickupLocationRequest.cs |
| Raw (PDF) result DTO | Sendy.Application/DTOs/Responses/Boxy/BoxyRawResult.cs |
| Province mapping | Sendy.Domain/Entities/Location/AddressProvince.cs → BoxyProvinceCode |
| Area mapping | Sendy.Domain/Entities/Location/AddressArea.cs → BoxyRegionName |
| Status column | Sendy.Domain/Entities/Orders/OrderExternalDelivery.cs → ExternalStatusLabel |
| Admin mapping endpoints | Sendy.Api/Controllers/V1/Admin/AdminDeliveryProvidersController.cs |
| DI registration | Sendy.Api/Hosting/WebApplicationBuilderExtensions.cs |
| EF migration | Sendy.Infrastructure/Migrations/20260605203917_AddBoxyProviderSupport.cs |
Source: DOCS/flows/boxy-delivery-provider-flow.md