Skip to content

Multi-Market API

Path-based routing API for market and language-specific product discovery with localized content.

The Multi-Market API provides endpoints for accessing products and configuration data scoped to specific geographical markets and languages. All product endpoints use path-based routing with both market code and language code in the URL path.

Base URL Pattern: /api/{market}/{lang}/...

Key Features:

  • Case-insensitive market and language codes (ES, es, Es all work)
  • Language-specific product content via locale
  • Content from ProductByMarketTranslation (no fallback to template)
  • Active product filtering with locale matching
  • Market configuration with supported languages
  • Structured error responses for invalid/inactive markets
Terminal window
curl -X GET "https://api.example.com/api/es/ca/products" \
-H "Accept: application/json"
Terminal window
curl -X GET "https://api.example.com/api/es/es/products/10" \
-H "Accept: application/json"
Terminal window
curl -X GET "https://api.example.com/api/es/ca/products/slug/tour-de-barcelona" \
-H "Accept: application/json"
Terminal window
curl -X GET "https://api.example.com/api/es/ca/products/sku/ES-2CMB10-CA1" \
-H "Accept: application/json"
Terminal window
curl -X GET "https://api.example.com/api/de/config" \
-H "Accept: application/json"

List all active products for a market and language with localized content.

Parameters:

NameInTypeRequiredDescription
marketpathstringYesMarket code (case-insensitive, e.g., “es”, “US”, “De”)
langpathstringYesLanguage code (e.g., “en”, “es”, “ca”)

Response: 200 OK

{
"data": [
{
"id": 10,
"product_template_id": 5,
"sku": "ES-5CMB10-CA1",
"locale": "ca_ES",
"status": "active",
"sort_order": 0,
"trip_duration_days": 10,
"title": "Tour de Sri Lanka",
"subtitle": "Descobreix l'illa maragda",
"short_description": "Una aventura increible...",
"long_description": "Descripció completa del tour...",
"highlights": ["Sigiriya", "Kandy", "Yala"],
"destination_info": "Sri Lanka",
"url_slug": "tour-sri-lanka",
"hero_image": "https://cdn.example.com/images/sri-lanka.jpg",
"departure_airports": [
{
"iata_code": "BCN",
"name": "Barcelona-El Prat Airport",
"city": "Barcelona",
"country": "Spain"
}
]
}
],
"meta": {
"market": "ES",
"locale": "ca_ES"
}
}

Get a single product by its ProductByMarket ID.

Parameters:

NameInTypeRequiredDescription
marketpathstringYesMarket code (case-insensitive)
langpathstringYesLanguage code
idpathintegerYesProductByMarket ID

GET /api/{market}/{lang}/products/slug/{slug}

Section titled “GET /api/{market}/{lang}/products/slug/{slug}”

Get a product by its URL slug. First checks translated slug, then falls back to base product template slug.

GET /api/{market}/{lang}/products/sku/{sku}

Section titled “GET /api/{market}/{lang}/products/sku/{sku}”

Get a product by its market-specific SKU code.

SKU Format: <MARKET>-<TEMPLATEID><IATA><DAYS>-<LANG><VERSION>

  • Example: ES-5CMB10-CA1 = Spain market, template 5, CMB airport, 10 days, Catalan, version 1

Get market configuration including locale, currency, timezone, supported languages, and departure airports.

Response: 200 OK

{
"data": {
"code": "ES",
"name": "Spain",
"locale": "es_ES",
"supported_languages": ["es", "ca"],
"currency": {
"code": "EUR",
"name": "Euro"
},
"timezone": "Europe/Madrid",
"departure_airports": [
{
"iata_code": "MAD",
"name": "Adolfo Suarez Madrid-Barajas Airport",
"city": "Madrid",
"is_primary": true
}
]
}
}
{
"success": false,
"error": "market_not_found",
"message": "Market 'xyz' not found."
}
{
"success": false,
"error": "market_inactive",
"message": "Market 'FR' is currently not available."
}
{
"success": false,
"error": "language_not_supported",
"message": "Language 'de' is not supported by market 'ES'. Supported languages: es, ca"
}
routes/api.php
|
v
Route::prefix('{market}')
->middleware(['market'])
->whereAlpha('market')
|
+-- GET /config -> ProductByMarketController@config
|
+-- Route::prefix('{lang}')->whereAlpha('lang')
|
+-- GET /products -> ProductByMarketController@index
+-- GET /products/{id} -> ProductByMarketController@show
+-- GET /products/slug/{slug} -> ProductByMarketController@showBySlug
+-- GET /products/sku/{sku} -> ProductByMarketController@showBySku
ComponentFilePurpose
Controllerapp/Http/Controllers/Api/ProductByMarketController.phpHandle requests, query data
Middlewareapp/Http/Middleware/ResolveMarket.phpValidate market, resolve locale
Product Resourceapp/Http/Resources/ProductByMarketResource.phpTransform product with translations
Market Resourceapp/Http/Resources/MarketResource.phpTransform market config
// Fetch products for a market and language
async function getMarketProducts(marketCode, langCode) {
const response = await fetch(`/api/${marketCode}/${langCode}/products`);
if (!response.ok) {
const error = await response.json();
throw new Error(error.message);
}
return response.json();
}
// Get market configuration
async function getMarketConfig(marketCode) {
const response = await fetch(`/api/${marketCode}/config`);
return response.json();
}
// Usage
const config = await getMarketConfig('es');
console.log('Supported languages:', config.data.supported_languages);

All product queries use eager loading to prevent N+1 queries:

->with(['productTemplate', 'translation', 'departureAirports'])
-- ProductByMarket queries
INDEX (market_id, status, sort_order)
-- Locale-based lookups
UNIQUE (product_template_id, market_id, locale)
-- Translation lookups
UNIQUE (locale, url_slug)

The ResolveMarket middleware automatically adds context to all logs:

Context::add('market_code', $market->code);
Context::add('market_id', $market->id);
Context::add('locale', $locale);