Feeef Cloud
Geo API

Catalog

Countries, states, cities, polygons, carriers, and pickups — the canonical place graph.

Countries → states → cities

GET /geo/countries
curl "https://apis.cloud.feeef.org/v1/geo/countries" \
  -H "Authorization: Bearer $FEEEF_CLOUD_KEY"
GET /geo/states?country=DZ&lang=fr
{ "data": [ {
    "code": "16", "country": "DZ", "name": "alger",
    "displayName": "Alger",
    "locales": { "ar": "الجزائر", "fr": "Alger", "en": "Algiers" },
    "center": { "lat": 36.7538, "lng": 3.0588 },
    "extentKm": { "width": 39.1, "height": 31.4 },
    "h3": "871f1a4d2ffffff"
} ], "meta": { "attribution": "© OpenStreetMap contributors (ODbL)" } }

code is the ISO-style two-digit wilaya code (01–58) — stable, and the right thing to store as a foreign key. name is a plain-ASCII slug form; displayName follows ?lang=.

GET /geo/cities?country=DZ&state=16&lang=ar

Same shape as a state, plus postalCode (nullable; availability depends on the dataset) and id, a stable composite key (DZ-16-bab-ezzouar) used everywhere else in the API that needs to reference a specific city: polygons, pickups, resolve results.

curl "https://apis.cloud.feeef.org/v1/geo/cities?country=DZ&state=16&lang=ar" \
  -H "Authorization: Bearer $FEEEF_CLOUD_KEY"

Boundary polygons

GET /geo/cities/:cityId/polygon?level=display

Kept out of the base city payload — most callers never need geometry, and a full polygon can be large. level=display is simplified for map rendering; level=full keeps more vertices for spatial calculations.

curl "https://apis.cloud.feeef.org/v1/geo/cities/DZ-16-bab-ezzouar/polygon?level=display" \
  -H "Authorization: Bearer $FEEEF_CLOUD_KEY"
{ "data": { "type": "Feature",
  "properties": { "cityId": "DZ-16-bab-ezzouar", "level": "display", "areaKm2": 8.9 },
  "geometry": { "type": "MultiPolygon", "coordinates": [ "…" ] } } }

Carriers and pickups

GET /geo/couriers?country=DZ

The carrier registry: which providers cover this country and what each one actually supports — create, cancel, edit, labels, pickups, webhooks, and batch size limits. This is what Parcels reads to decide whether an operation is even possible before trying it, and it's the same list an integration should check before assuming a carrier can do something (e.g. not every carrier can edit a submitted parcel).

GET /geo/pickups?country=DZ&courier=yalidine&state=16

Cross-carrier stop-desk points, keyed to the same canonical city.id the catalog uses. If you're building a stop-desk picker for checkout: resolve the customer's address first (Resolve), then filter pickups by the resolved city, not by matching the pickup's own free-text city name against anything.

GET /geo/couriers/:id/cities?state=16&hasPickup=true

Which communes a courier accepts stop-desk parcels for. Filter server-side with hasPickup=true rather than fetching the whole wilaya and checking in your own code, and use city=<name> when you only need one commune.

A desk nearby does not mean stop-desk is accepted

These are two different questions and carriers answer them differently. hasPickup is the one their order API enforces: submit a stop-desk parcel to a commune where it is false and the carrier rejects it — for Ecotrack, Stop desk non disponible pour cette commune — after the call is already spent.

The desk list is not that signal. 48hr has an agency in Hussein Dey and refuses stop-desk parcels addressed there, while accepting them for Mohammadia where it has no agency at all. Check hasPickup on the destination commune, then let the customer choose a desk.

GET /geo/carriers remains a deprecated compatibility alias. New integrations should use /geo/couriers; the repository SDK's geo.carriers helper currently uses the alias.

On this page