Multi-Market API
Path-based routing API for market and language-specific product discovery with localized content.
Overview
Section titled “Overview”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
Quick Start
Section titled “Quick Start”Get Market Products (with language)
Section titled “Get Market Products (with language)”curl -X GET "https://api.example.com/api/es/ca/products" \ -H "Accept: application/json"Get Single Product by ID
Section titled “Get Single Product by ID”curl -X GET "https://api.example.com/api/es/es/products/10" \ -H "Accept: application/json"Get Single Product by Slug
Section titled “Get Single Product by Slug”curl -X GET "https://api.example.com/api/es/ca/products/slug/tour-de-barcelona" \ -H "Accept: application/json"Get Single Product by SKU
Section titled “Get Single Product by SKU”curl -X GET "https://api.example.com/api/es/ca/products/sku/ES-2CMB10-CA1" \ -H "Accept: application/json"Get Market Configuration
Section titled “Get Market Configuration”curl -X GET "https://api.example.com/api/de/config" \ -H "Accept: application/json"Endpoints
Section titled “Endpoints”GET /api/{market}/{lang}/products
Section titled “GET /api/{market}/{lang}/products”List all active products for a market and language with localized content.
Parameters:
| Name | In | Type | Required | Description |
|---|---|---|---|---|
| market | path | string | Yes | Market code (case-insensitive, e.g., “es”, “US”, “De”) |
| lang | path | string | Yes | Language 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 /api/{market}/{lang}/products/{id}
Section titled “GET /api/{market}/{lang}/products/{id}”Get a single product by its ProductByMarket ID.
Parameters:
| Name | In | Type | Required | Description |
|---|---|---|---|---|
| market | path | string | Yes | Market code (case-insensitive) |
| lang | path | string | Yes | Language code |
| id | path | integer | Yes | ProductByMarket 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 /api/{market}/config
Section titled “GET /api/{market}/config”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 } ] }}Error Responses
Section titled “Error Responses”Market Not Found (404)
Section titled “Market Not Found (404)”{ "success": false, "error": "market_not_found", "message": "Market 'xyz' not found."}Market Inactive (403)
Section titled “Market Inactive (403)”{ "success": false, "error": "market_inactive", "message": "Market 'FR' is currently not available."}Language Not Supported (400)
Section titled “Language Not Supported (400)”{ "success": false, "error": "language_not_supported", "message": "Language 'de' is not supported by market 'ES'. Supported languages: es, ca"}Architecture
Section titled “Architecture”Route Structure
Section titled “Route Structure”routes/api.php | vRoute::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@showBySkuComponents
Section titled “Components”| Component | File | Purpose |
|---|---|---|
| Controller | app/Http/Controllers/Api/ProductByMarketController.php | Handle requests, query data |
| Middleware | app/Http/Middleware/ResolveMarket.php | Validate market, resolve locale |
| Product Resource | app/Http/Resources/ProductByMarketResource.php | Transform product with translations |
| Market Resource | app/Http/Resources/MarketResource.php | Transform market config |
Frontend Integration (JavaScript)
Section titled “Frontend Integration (JavaScript)”// Fetch products for a market and languageasync 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 configurationasync function getMarketConfig(marketCode) { const response = await fetch(`/api/${marketCode}/config`); return response.json();}
// Usageconst config = await getMarketConfig('es');console.log('Supported languages:', config.data.supported_languages);Performance
Section titled “Performance”Eager Loading
Section titled “Eager Loading”All product queries use eager loading to prevent N+1 queries:
->with(['productTemplate', 'translation', 'departureAirports'])Database Indexes
Section titled “Database Indexes”-- ProductByMarket queriesINDEX (market_id, status, sort_order)
-- Locale-based lookupsUNIQUE (product_template_id, market_id, locale)
-- Translation lookupsUNIQUE (locale, url_slug)Context Logging
Section titled “Context Logging”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);