Locations and Zones (Places)

A base path different from /inventory

Locations live at https://api.fenicia.io/locations/* — a different base path and service from /inventory/*. They have their own permission namespace (locations:*), their own pagination convention, and their own response shape. Don't assume the rules documented for /inventory/* apply here.

A Location (Location) is a warehouse, physical store, a marketplace fulfillment center (FBA, Mercado Full), or any other point where stock is kept or managed. A Place is a zone within a location — a shelf, a receiving zone, a damaged-goods zone — which can in turn be broken down into placements (aisle, island, endcap, bin, rack, etc.), the exact physical position within that zone.

Data model

Location

id, locationCode? (unique per tenant, sparse), tenantId, name,
type (enum, see below, default 'principal'), active (bool, default true),
isDefault?, isSystem?,
locationAddress?: Address, phoneNumber?, email?, contactName?,
location?: { type: 'Point', coordinates: number[] },
capabilities?: Capability[] (enum, see below),
channelBindings?: [{ channel, mappingId, overrides: [{ path, value }] }],
places?: Place[],
inventoryHandler?: {
  type ('manual'|'integration'|'channel', default 'manual'),
  sourceId?, sourceName?, sourceLocationId?, sourceLocationName?,
  allowManualOverride? (bool, default false)
}

type values: principal, warehouse, store, factory, service, fba, mercado-full, claro-express, shopify-fullfillment, fenicia-managed, virtual.

shopify-fullfillment is spelled this way

shopify-fullfillment (with a double "ll") is the literal enum value in production — it is not a typo in this documentation. Do not "fix" it to shopify-fulfillment in your integration; the exact value is the one you see here.

capabilities values: pos, pick-up, fulfillment, inventory, one-way-sync, two-way-sync.

Place

id, code, type (enum, see below), name, description?, active (default true),
maxCapacity?, pickPriority?, availableForSale?,
placements?: [{ code, type (enum, see below), label? }]

type values (zone): on-shelf, backstock, receiving, returns, damaged, mezzanine, showcase, cold-storage, custom.

type values (placement, position within the zone): aisle, island, endcap, shelf, bin, rack, bay, pallet, display, counter, custom.

Pagination and response shape — different from /inventory/*

Advertencia

GET /locations paginates 0-indexed with a default limit of 10 (not 20). Its response is { locations, totalCount, page, limit } — note totalCount, not total as in the rest of the inventory domain.

List locations

Lists the tenant's locations with filters. Requires locations:read.

pagenumber

Page, 0-based. Default: 0.

limitnumber

Results per page. Default: 10.

searchstring

Search by name/code.

typestring

Filter by type.

activeboolean

Filter by active status.

capabilitiesstring

Comma-separated list of capabilities.

200
{
  "locations": [
    { "id": "loc_cedis_cdmx", "name": "CEDIS Ciudad de México", "type": "warehouse", "active": true, "isDefault": true }
  ],
  "totalCount": 6,
  "page": 0,
  "limit": 10
}
500
{ "code": "internal-server-error", "message": "..." }

Count locations

Counts the tenant's locations. Requires locations:read.

200
{ "count": 6 }
400
{ "code": "bad-request/not-found", "message": "..." }

Información

This endpoint returns 400 (not 404) with code: "bad-request/not-found" when the service cannot resolve the count. It's an unusual code/status combination — documented as it behaves today, not "fixed".

Get a location

Gets a location by ID or by locationCode (unified lookup). Requires locations:read.

idstringrequired

MongoDB ID or locationCode — both resolve through the same path param.

200
{ "location": { "id": "loc_cedis_cdmx", "name": "CEDIS Ciudad de México", "type": "warehouse" } }
404
{ "code": "not-found", "message": "Location not found" }

The only endpoint in this domain that uses code:'not-found' on a 404

Within lambda-locations, only GET /locations/{id} uses the literal code not-found on a 404. Every other 404 in this domain (see the table below) uses bad-request/not-found — a bad-request/-prefixed code on a 404 status. Documented literally, this is not a typo in this guide.

Create a location

Creates a new location. Requires locations:create.

namestringrequired

Location name.

typestringrequired

One of the type enum values (see Data model).

locationCodestring

Unique code per tenant.

activeboolean

Default: true.

isDefaultboolean

Marks the location as the tenant's default.

isSystemboolean

Marks the location as system-managed (not deletable).

capabilitiesstring[]

Subset of the capabilities enum.

inventoryHandlerobject

Inventory handling configuration (manual/integration/channel).

{
  "name": "Tienda Polanco",
  "type": "store",
  "locationCode": "TDA-POL",
  "active": true,
  "capabilities": ["pos", "pick-up"]
}
201
{ "location": { "id": "loc_new123", "name": "Tienda Polanco", "type": "store", "active": true } }

Información

capabilities is not validated against the catalog on create or update — only type is checked against its enum. A capabilities value that doesn't exist in the catalog is silently accepted. (Code observation: not confirmed with an independent negative test; it is the absence of a catalog check for capabilities in the controller, analogous to the one that does exist for type.)

Update a location

Partially updates a location. Requires locations:update.

idstringrequired

Location ID.

typestring

If sent, is revalidated against the type enum.

{ "name": "Tienda Polanco Centro" }
200
{ "location": { "id": "loc_new123", "name": "Tienda Polanco Centro", "type": "store" } }

Accepts any subset of Location fields in the body.

Set default location

Marks (or unmarks) a location as the tenant's default. Requires locations:update.

idstringrequired

Location ID.

isDefaultboolean

Optional body.

{ "isDefault": true }
200
{
  "message": "Default location updated",
  "defaultLocation": { "id": "loc_cedis_cdmx", "isDefault": true }
}
400
{ "code": "bad-request/system-location", "message": "Cannot remove default flag from a system location" }

Two-step write, no transaction

Internally, marking a location as default first clears isDefault on all other locations of the tenant (updateMany) and then marks the target location — these are two separate writes, without a Mongo session/transaction wrapping them. In theory, a failure between the two steps could leave the tenant with no default location at all. This is an observation about the shape of the code, not a confirmed production incident.

Delete a location

Deletes a location. Requires locations:delete.

idstringrequired

Location ID.

200
{ "locationDelete": { "acknowledged": true, "deletedCount": 1 } }
400
{ "code": "bad-request/system-location", "message": "Cannot delete a system location" }

No referential integrity check

Deleting a location does not check whether Inventory records still reference it as locationId. It is possible to leave orphaned inventory records pointing at a deleted location.

Zones (places) of a location

Lists the zones (places) of a location. Requires locations:read.

idstringrequired

Location ID.

200
{
  "places": [
    { "id": "plc_01H...", "code": "A-01", "type": "on-shelf", "name": "Anaquel A-01", "active": true }
  ],
  "total": 1
}

Create a zone

Creates a zone within a location. Requires locations:update.

idstringrequired

Location ID.

codestringrequired

Zone code, unique within the location.

namestringrequired

Zone name.

typestringrequired

on-shelf, backstock, receiving, returns, damaged, mezzanine, showcase, cold-storage, or custom.

descriptionstring

Free-text description.

activeboolean

Default: true.

maxCapacitynumber

Maximum capacity of the zone.

pickPrioritynumber

If omitted, defaulted based on type.

availableForSaleboolean

If omitted, defaulted based on type.

placementsarray

Positions within the zone (aisle, island, endcap, shelf, bin, rack, bay, pallet, display, counter, custom).

{
  "code": "B-02",
  "name": "Backstock B-02",
  "type": "backstock"
}
201
{
  "place": { "id": "plc_new456", "code": "B-02", "type": "backstock", "name": "Backstock B-02", "pickPriority": 5, "availableForSale": false },
  "location": { "id": "loc_cedis_cdmx", "places": [ "..." ] }
}

Información

The zone's id is generated by the server (UUID). Uniqueness of code within a location is checked in memory at creation time, not via a database unique index.

Update a zone

Partially updates a zone. Requires locations:update.

idstringrequired

Location ID.

placeIdstringrequired

Zone ID.

{ "name": "Backstock B-02 Norte" }
200
{ "place": { "id": "plc_new456", "code": "B-02", "name": "Backstock B-02 Norte" }, "location": { "id": "loc_cedis_cdmx" } }

Accepts any subset of Place fields and merges onto the existing zone.

Delete a zone

Deletes a zone from a location. Requires locations:update.

idstringrequired

Location ID.

placeIdstringrequired

Zone ID.

200
{ "message": "Place deleted", "deletedPlace": { "id": "plc_new456", "code": "B-02" }, "location": { "id": "loc_cedis_cdmx" } }

Errors

CodeHTTPCause
not-found404GET /locations/{id} — the location does not exist (the only endpoint in this domain with this literal code on a 404).
bad-request/not-found404The rest of the ID-lookup endpoints (places, set-default, delete) when the resource doesn't exist.
bad-request/not-found400GET /locations/count when the service cannot resolve the count.
bad-request/system-location400You tried to delete a system location, or remove its isDefault flag.
bad-request/missing-id400PUT /locations/{id} called without the id route parameter.
bad-request/can't-create400The service could not create the location.
bad-request/can't-update400The service could not update the location.
bad-request/can't-delete400The service could not delete the location.
internal-server-error500Generic error from the locations lambda.

See the Error Catalog for the full detail, including the common authentication, permission, and tenant errors.

Examples

curl -G https://api.fenicia.io/locations \
  -H "Authorization: Bearer fkapi_your_api_key" \
  --data-urlencode "type=warehouse" \
  --data-urlencode "active=true"
 
curl -X POST https://api.fenicia.io/locations/loc_cedis_cdmx/places \
  -H "Authorization: Bearer fkapi_your_api_key" \
  -H "Content-Type: application/json" \
  -d '{"code":"B-02","name":"Backstock B-02","type":"backstock"}'

Next steps