.dev

Shipping is a parcel now

Carrier accounts, fee cards, quotes, stop desks, labels, and what happens when the commune is not covered.

FeeefFeeef engineering

The old shipping screen was a price matrix and a pile of carrier cards. Each company had its own send call, its own label call, and its own idea of a wilaya code. The merchant app still has those cards for stores that connected them years ago. The hub at /shipping does not use them.

The hub talks to the Cloud parcel API. One account per company, one parcel record, one way to quote, print, and track. The route list is the shipping guide. Places — the commune, the desk, the coverage map — are Cloud geo.

A company is an account

GET /v1/stores/{storeId}/couriers is the catalog: Ecotrack, Yalidine, Guepex, ZR Express, E-com Delivery, NOEST, Maystro, MDM Express, Procolis, Zimou, Feeef Express, plus other when you only want a manual price card.

POST /v1/stores/{storeId}/carrier-accounts connects one. The id looks like ca_…. Ecotrack can skip the token paste: POST …/carrier-accounts/ecotrack-login signs into the company portal and stores the token. Yalidine and Guepex need the origin wilaya. config.autoValidate on the account tells Cloud to validate the parcel on send when the company allows it.

The store's default fee is a field called shippingFeeId. It is either that ca_… or the literal unassigned, which means the shared project fee card. It is not named carrierAccountId. That name belongs on the send and quote body: "use this account for this parcel."

Fees, then a quote

Each account has a fee matrix, home and stop-desk, by wilaya and sometimes by commune. PUT …/carrier-accounts/{accountId}/fees replaces it. POST …/fees/import pulls the company's own tariff when the company supports that (Maystro, MDM, Procolis, Zimou, and Feeef Express do not).

The project card lives at GET /v1/stores/{storeId}/courier-fees/unassigned. Order pricing walks product shippingFeeId, then the store default, then the cheapest account. A ca_… id goes through POST …/carrier-accounts/fees/resolve. unassigned picks the row for the customer's state.

A live quote is one post:

curl -X POST "https://api.feeef.org/v1/stores/$STORE/carrier-accounts/quotes" \
  -H "Authorization: Bearer $FEEEF_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{ "state": "16", "city": "Bab Ezzouar", "deliveryType": "home" }'

deliveryType is home, pickup, or store. Pass shippingCity when you calculate an order so a commune-level row can win over the wilaya flat rate.

The parcel

Create with POST …/parcels, change a draft with PATCH, hand it to the company with POST …/parcels/{id}/send. From an order you can skip the draft:

curl -X POST "https://api.feeef.org/v1/stores/$STORE/orders/$ORDER/cloud-parcels/send" \
  -H "Authorization: Bearer $FEEEF_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{ "carrierAccountId": "ca_…" }'

The response carries cloudParcelId and trackingId. POST …/cloud-parcels/sendMany takes orderIds or items — up to 500 in the merchant app. That is still one Feeef request. Cloud then splits it into the courier's own batch size and waits between chunks. Sending 500 orders is that path. POST …/cloud-parcels/unsend cancels and detaches.

If the company does not cover that commune, send rewrites the destination to the nearest covered stop desk. That fallback is nearest_covered. Set coverageFallback to none when you would rather fail. Stop-desk delivery sends pickupId (pkp_… from GET /v1/geo/pickups).

Tracking comes back on POST /v1/integrations/cloud/parcels/webhook (HMAC, no user session) and is readable at GET …/parcels/{id}/events. You can also sync one parcel, sync the whole account, or import parcels that already exist at the company and were never created in Feeef.

Labels and the desk

GET …/parcels/{id}/label is one label. POST …/parcels/labels is many. The company's PDF is the default. label-settings and label-templates are the Feeef layout when you want your own mark on the sticker.

The desk the customer walks to is geo, not a hardcoded wilaya list. Search is GET /v1/geo/search?q=. Coverage is GET /v1/geo/couriers/{courierId}/cities. A pin is GET /v1/geo/reverse?lat=&lng=. The playground signs in with Feeef and runs those calls.

The older …/carriers/{code}/send routes still exist. They are the legacy bridge. A new integration should create a carrier account and send a Cloud parcel. One company, one fee card, one parcel id — that is the whole shipping system.

On this page