{
  "info": {
    "name": "🌍 Country Flags API",
    "_postman_id": "c8f1a234-b567-4d90-abcd-ef1234567890",
    "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json",
    "description": "Open-source REST API serving ISO 3166-1 country metadata, SVG flag assets, and city data.\n\nBase URL: {{baseUrl}}\n\nResponse shapes:\n  Single resource  →  { data }\n  List (countries) →  { count, data }\n  List (cities)    →  { total, data }\n  API index        →  { name, version, endpoints }\n  Flag             →  raw SVG  (Content-Type: image/svg+xml)\n  Error            →  { error: { code, message } }\n\nNo authentication required — fully public API."
  },
  "variable": [
    { "key": "baseUrl", "value": "https://iso.api.gbstr.io", "type": "string" }
  ],
  "item": [
    {
      "name": "🏠 General",
      "description": "Namespace: /\n\nGeneral utility endpoints for API discovery and liveness monitoring.\n\nRoutes:\n  GET  /        →  📋  API index — lists all available endpoints with their methods and paths\n  GET  /health  →  💓  Health check — confirms the Node.js process is alive and reports uptime",
      "item": [
        {
          "name": "📋 API Index",
          "description": "Returns a self-documenting map of every available endpoint in the API.\n\n**Access:** Public — no authentication required\n**Returns:** 200 + `{ name, version, endpoints }` where `endpoints` is a map of human-readable names to `METHOD /path` strings\n\n**Validations / notes:**\n- Use this as the entry-point discovery call to introspect the API\n- The `endpoints` object always reflects the current live route set",
          "request": {
            "method": "GET",
            "url": {
              "raw": "{{baseUrl}}/",
              "query": []
            },
            "header": []
          }
        },
        {
          "name": "💓 Health Check",
          "description": "Liveness probe — confirms the Node.js process is running and returns its uptime in seconds.\n\n**Access:** Public — no authentication required\n**Returns:** 200 + `{ status: 'ok', uptime: number }` where `uptime` is `process.uptime()` in seconds\n\n**Validations / notes:**\n- Wire this to your load balancer, Docker HEALTHCHECK, or Kubernetes liveness probe\n- If the process is down this endpoint will not respond at all",
          "request": {
            "method": "GET",
            "url": {
              "raw": "{{baseUrl}}/health",
              "query": []
            },
            "header": []
          }
        }
      ]
    },
    {
      "name": "🌍 Countries",
      "description": "Namespace: /v1/countries\n\nProvides full ISO 3166-1 country metadata: names, codes, capitals, regions, calling codes, currencies, languages, coordinates, and absolute flag URLs.\n\nRoutes:\n  GET  /       →  📋  List all countries (optional ?iso=true filter)\n  GET  /:code  →  🔍  Get a single country by alpha-2 or alpha-3 code",
      "item": [
        {
          "name": "📋 List All Countries",
          "description": "Returns metadata for every country in the dataset, including both strict ISO 3166-1 entries and extended regional/territory entries.\n\n**Access:** Public — no authentication required\n**Returns:** 200 + `{ count: number, data: Country[] }`\n\n**Country object shape:**\n| Field | Type | Notes |\n|---|---|---|\n| code | string | ISO 3166-1 alpha-2 (e.g. `EG`) |\n| alpha3 | string \\| null | ISO 3166-1 alpha-3 (e.g. `EGY`) |\n| numeric | string \\| null | ISO 3166-1 numeric (e.g. `818`) |\n| name | string | Common name (e.g. `Egypt`) |\n| official | string | Official full name |\n| capital | string \\| null | Capital city |\n| region | string \\| null | World region |\n| subregion | string \\| null | Sub-region |\n| callingCode | string \\| null | E.164 prefix (e.g. `+20`) |\n| tld | string[] | Top-level domains |\n| currencies | string[] | Currency codes |\n| languages | string[] | Language names |\n| latlng | number[] | `[latitude, longitude]` |\n| area | number \\| null | Area in km² |\n| unMember | boolean | UN membership status |\n| iso | boolean | Whether this is a strict ISO 3166-1 entry |\n| flags | object | `{ '1x1': url, '4x3': url }` — absolute API URLs to the SVG endpoints |\n\n**Query params:**\n| Param | Type | Description |\n|---|---|---|\n| iso | `true` \\| `false` | When `true`, narrows to strict ISO 3166-1 entries only. Default: `false` |",
          "request": {
            "method": "GET",
            "url": {
              "raw": "{{baseUrl}}/v1/countries",
              "query": [
                {
                  "key": "iso",
                  "value": "false",
                  "description": "Narrow to strict ISO 3166-1 entries only.\nValues: true = ISO countries only | false = all entries including territories (default: false)",
                  "disabled": true
                }
              ]
            },
            "header": []
          }
        },
        {
          "name": "📋 List ISO Countries Only (?iso=true)",
          "description": "Returns only the strict ISO 3166-1 recognized countries, excluding territories, disputed regions, and special codes.\n\n**Access:** Public — no authentication required\n**Returns:** 200 + `{ count: number, data: Country[] }` — same shape as the full list but narrowed to `iso: true` entries only\n\n**Validations / notes:**\n- The `count` field reflects the filtered total, not the full dataset size\n- Every item in `data` will have `iso: true`",
          "request": {
            "method": "GET",
            "url": {
              "raw": "{{baseUrl}}/v1/countries",
              "query": [
                {
                  "key": "iso",
                  "value": "true",
                  "description": "Narrow to strict ISO 3166-1 entries only.\nValues: true = ISO countries only | false = all entries (default: false)"
                }
              ]
            },
            "header": []
          }
        },
        {
          "name": "🔍 Get Country by Alpha-2 Code",
          "description": "Returns the full metadata object for a single country identified by its 2-letter ISO 3166-1 alpha-2 code.\n\n**Access:** Public — no authentication required\n**Returns:** 200 + `{ data: Country }` — single country object with all fields and absolute flag URLs\n\n**Validations / notes:**\n- Code is case-insensitive — `EG`, `eg`, and `Eg` all resolve to the same country\n- Returns 400 if the input is not a 2–3 letter alphabetic string\n- Returns 404 if the code does not match any known country\n\n**Path variable:**\n| Param | Type | Example | Description |\n|---|---|---|---|\n| code | string | `EG` | ISO 3166-1 alpha-2 code — 2 letters, case-insensitive |",
          "request": {
            "method": "GET",
            "url": {
              "raw": "{{baseUrl}}/v1/countries/:code",
              "variable": [
                {
                  "key": "code",
                  "value": "EG",
                  "description": "ISO 3166-1 alpha-2 code — 2 letters, case-insensitive. Examples: EG (Egypt) | US (United States) | GB (United Kingdom) | DE (Germany) | JP (Japan)"
                }
              ]
            },
            "header": []
          }
        },
        {
          "name": "🔍 Get Country by Alpha-3 Code",
          "description": "Returns the full metadata object for a single country identified by its 3-letter ISO 3166-1 alpha-3 code.\n\n**Access:** Public — no authentication required\n**Returns:** 200 + `{ data: Country }` — identical shape to the alpha-2 lookup\n\n**Validations / notes:**\n- Code is case-insensitive — `EGY`, `egy`, and `Egy` all resolve\n- Returns 400 if the input is not a 2–3 letter alphabetic string\n- Returns 404 if the alpha-3 code does not match any known country\n\n**Path variable:**\n| Param | Type | Example | Description |\n|---|---|---|---|\n| code | string | `EGY` | ISO 3166-1 alpha-3 code — 3 letters, case-insensitive |",
          "request": {
            "method": "GET",
            "url": {
              "raw": "{{baseUrl}}/v1/countries/:code",
              "variable": [
                {
                  "key": "code",
                  "value": "EGY",
                  "description": "ISO 3166-1 alpha-3 code — 3 letters, case-insensitive. Examples: EGY (Egypt) | USA (United States) | GBR (United Kingdom) | DEU (Germany) | JPN (Japan)"
                }
              ]
            },
            "header": []
          }
        },
        {
          "name": "❌ Get Country — Not Found (404)",
          "description": "Demonstrates the 404 error response when the code is valid format but does not match any country in the dataset.\n\n**Access:** Public\n**Returns:** 404 + `{ error: { code: 'NOT_FOUND', message: 'No country found for code \"XX\".' } }`\n\n**Validations / notes:**\n- `XX` is a syntactically valid alpha-2 code but is not assigned to any country\n- Use this request to verify your client handles 404 errors correctly",
          "request": {
            "method": "GET",
            "url": {
              "raw": "{{baseUrl}}/v1/countries/:code",
              "variable": [
                {
                  "key": "code",
                  "value": "XX",
                  "description": "Syntactically valid 2-letter code with no matching country — guaranteed 404"
                }
              ]
            },
            "header": []
          }
        },
        {
          "name": "❌ Get Country — Bad Request (400)",
          "description": "Demonstrates the 400 error response when the code fails the format validation rule (must be 2–3 alphabetic letters).\n\n**Access:** Public\n**Returns:** 400 + `{ error: { code: 'BAD_REQUEST', message: 'Invalid country code \"12345\"...' } }`\n\n**Validations / notes:**\n- The normalizer rejects any input that is not exactly 2 or 3 uppercase letters\n- Numbers, symbols, single letters, or strings longer than 3 chars all trigger 400",
          "request": {
            "method": "GET",
            "url": {
              "raw": "{{baseUrl}}/v1/countries/:code",
              "variable": [
                {
                  "key": "code",
                  "value": "12345",
                  "description": "Invalid code — numeric string, fails the /^[A-Z]{2,3}$/ format check — guaranteed 400"
                }
              ]
            },
            "header": []
          }
        }
      ]
    },
    {
      "name": "🚩 Flags",
      "description": "Namespace: /v1/flags\n\nServes raw SVG flag images for countries by code. Responses are `image/svg+xml` with `Cache-Control: public, max-age=86400` — safe to embed directly via `<img src>` tags or CSS `background-image`.\n\nTwo aspect ratios are available:\n  4x3  — rectangular (landscape), default when style is omitted\n  1x1  — square, ideal for icons and avatars\n\nRoutes:\n  GET  /:code  →  🚩  Serve flag SVG (optional ?style=1x1|4x3)",
      "item": [
        {
          "name": "🚩 Get Flag — 4x3 Rectangular (Default)",
          "description": "Returns the raw SVG flag in the default rectangular 4:3 aspect ratio.\n\n**Access:** Public — no authentication required\n**Returns:** 200 + raw SVG markup\n  - `Content-Type: image/svg+xml`\n  - `Cache-Control: public, max-age=86400` (safe to cache for 24 hours — assets are immutable)\n\n**Validations / notes:**\n- Response body is raw SVG, not JSON — configure your client to accept `image/svg+xml`\n- Omitting `style` is equivalent to `?style=4x3`\n- Returns 404 if the country code does not exist in the dataset\n- Returns 400 if the code fails the 2–3 letter format validation\n- Returns 400 if `style` is provided but is not `1x1` or `4x3`\n\n**Path variable:**\n| Param | Type | Example | Description |\n|---|---|---|---|\n| code | string | `EG` | ISO 3166-1 alpha-2 or alpha-3 code, case-insensitive |",
          "request": {
            "method": "GET",
            "url": {
              "raw": "{{baseUrl}}/v1/flags/:code",
              "variable": [
                {
                  "key": "code",
                  "value": "EG",
                  "description": "ISO 3166-1 alpha-2 or alpha-3 code, case-insensitive. Examples: EG | EGY | US | USA | GB | GBR"
                }
              ],
              "query": []
            },
            "header": [
              { "key": "Accept", "value": "image/svg+xml", "description": "Flag response is SVG, not JSON" }
            ]
          }
        },
        {
          "name": "🚩 Get Flag — 1x1 Square (?style=1x1)",
          "description": "Returns the raw SVG flag in a square 1:1 aspect ratio — ideal for icons, avatars, and compact list items.\n\n**Access:** Public — no authentication required\n**Returns:** 200 + raw SVG markup\n  - `Content-Type: image/svg+xml`\n  - `Cache-Control: public, max-age=86400`\n\n**Validations / notes:**\n- Same as the 4x3 endpoint but the SVG canvas is square\n- Response is raw SVG, not JSON\n\n**Path variable:**\n| Param | Type | Example | Description |\n|---|---|---|---|\n| code | string | `EG` | ISO 3166-1 alpha-2 or alpha-3 code, case-insensitive |\n\n**Query param:**\n| Param | Values | Description |\n|---|---|---|\n| style | `1x1` \\| `4x3` | Flag aspect ratio. `1x1` = square. Default: `4x3` |",
          "request": {
            "method": "GET",
            "url": {
              "raw": "{{baseUrl}}/v1/flags/:code",
              "variable": [
                {
                  "key": "code",
                  "value": "EG",
                  "description": "ISO 3166-1 alpha-2 or alpha-3 code, case-insensitive"
                }
              ],
              "query": [
                {
                  "key": "style",
                  "value": "1x1",
                  "description": "Flag aspect ratio.\nValues: 1x1 = square/icon | 4x3 = rectangular/landscape (default: 4x3)"
                }
              ]
            },
            "header": [
              { "key": "Accept", "value": "image/svg+xml", "description": "Flag response is SVG, not JSON" }
            ]
          }
        },
        {
          "name": "❌ Get Flag — Invalid Style (400)",
          "description": "Demonstrates the 400 error when an unsupported value is passed to the `style` query param.\n\n**Access:** Public\n**Returns:** 400 + `{ error: { code: 'BAD_REQUEST', message: 'Invalid style \"2x3\". Use \"1x1\" or \"4x3\".' } }`\n\n**Validations / notes:**\n- Only `1x1` and `4x3` are accepted\n- Any other value — `square`, `2x3`, `full`, `large` — returns 400",
          "request": {
            "method": "GET",
            "url": {
              "raw": "{{baseUrl}}/v1/flags/:code",
              "variable": [
                {
                  "key": "code",
                  "value": "EG",
                  "description": "Valid country code — error is triggered by the style param, not the code"
                }
              ],
              "query": [
                {
                  "key": "style",
                  "value": "2x3",
                  "description": "Invalid style value — only 1x1 and 4x3 are accepted. Any other value returns 400."
                }
              ]
            },
            "header": []
          }
        },
        {
          "name": "❌ Get Flag — Country Not Found (404)",
          "description": "Demonstrates the 404 error when the country code is valid format but has no matching entry.\n\n**Access:** Public\n**Returns:** 404 + `{ error: { code: 'NOT_FOUND', message: 'No flag found for code \"XX\".' } }`",
          "request": {
            "method": "GET",
            "url": {
              "raw": "{{baseUrl}}/v1/flags/:code",
              "variable": [
                {
                  "key": "code",
                  "value": "XX",
                  "description": "Valid format but unrecognized country code — guaranteed 404"
                }
              ],
              "query": []
            },
            "header": []
          }
        }
      ]
    },
    {
      "name": "🏙️ Cities",
      "description": "Namespace: /v1/cities\n\nProvides a paginated, searchable list of cities for any country. Data sourced from GeoNames Gazetteer (via cities.json npm package, updated monthly). The dataset is auto-updated in the running server every 24 hours — no restarts required.\n\nRoutes:\n  GET  /:code  →  🏙️  List cities for a country (paginated + optional name search)",
      "item": [
        {
          "name": "🏙️ Get Cities for a Country",
          "description": "Returns a paginated list of cities for the given country code, optionally filtered by a city name search string.\n\n**Access:** Public — no authentication required\n**Returns:** 200 + `{ total: number, data: City[] }`\n  - `total` — count of all matching cities (after search filter, before pagination)\n  - `data` — the paginated slice\n\n**City object shape:**\n| Field | Type | Description |\n|---|---|---|\n| name | string | City name |\n| lat | number | Latitude |\n| lng | number | Longitude |\n\n**Validations / notes:**\n- Code is case-insensitive — `EG`, `eg`, and `EGY` all resolve\n- Returns 400 if the code fails the 2–3 letter format check\n- Returns 404 if the country code does not exist\n- `search` is a partial, case-insensitive match on the city name\n- Cities are sorted alphabetically within each country\n\n**Path variable:**\n| Param | Type | Example | Description |\n|---|---|---|---|\n| code | string | `EG` | ISO 3166-1 alpha-2 or alpha-3 code, case-insensitive |\n\n**Query params:**\n| Param | Default | Description |\n|---|---|---|\n| limit | 20 | Max number of cities to return per page |\n| skip | 0 | Number of cities to skip (offset for pagination) |\n| search | — | Partial city name filter (case-insensitive) |",
          "request": {
            "method": "GET",
            "url": {
              "raw": "{{baseUrl}}/v1/cities/:code",
              "variable": [
                {
                  "key": "code",
                  "value": "EG",
                  "description": "ISO 3166-1 alpha-2 or alpha-3 code, case-insensitive. Examples: EG | EGY | US | USA | GB | GBR"
                }
              ],
              "query": [
                {
                  "key": "limit",
                  "value": "20",
                  "description": "Max cities to return per page. Default: 20",
                  "disabled": true
                },
                {
                  "key": "skip",
                  "value": "0",
                  "description": "Number of cities to skip (pagination offset). Default: 0",
                  "disabled": true
                },
                {
                  "key": "search",
                  "value": "",
                  "description": "Partial city name filter — case-insensitive. Example: search=alex returns Alexandria, etc.",
                  "disabled": true
                }
              ]
            },
            "header": []
          }
        },
        {
          "name": "🏙️ Get Cities — Paginated (Page 2)",
          "description": "Demonstrates fetching the second page of cities for a country using `limit` and `skip`.\n\n**Access:** Public\n**Returns:** 200 + `{ total, data }` — same shape; `total` stays constant across pages, `data` contains items 20–39\n\n**Validations / notes:**\n- Use `total` from the first response to compute how many pages exist: `Math.ceil(total / limit)`",
          "request": {
            "method": "GET",
            "url": {
              "raw": "{{baseUrl}}/v1/cities/:code",
              "variable": [
                {
                  "key": "code",
                  "value": "EG",
                  "description": "ISO 3166-1 alpha-2 or alpha-3 code, case-insensitive"
                }
              ],
              "query": [
                {
                  "key": "limit",
                  "value": "20",
                  "description": "Items per page. Default: 20"
                },
                {
                  "key": "skip",
                  "value": "20",
                  "description": "Skip first 20 results to get page 2"
                }
              ]
            },
            "header": []
          }
        },
        {
          "name": "🔎 Get Cities — Search by Name",
          "description": "Returns all cities whose name partially matches the search string (case-insensitive). Pagination still applies on top of the filtered result set.\n\n**Access:** Public\n**Returns:** 200 + `{ total, data }` — `total` reflects the filtered count, not the full country total\n\n**Validations / notes:**\n- `search=alex` matches Alexandria, Al Iskandariyah, etc.\n- Search runs on the already-sorted city list, so results are still alphabetical",
          "request": {
            "method": "GET",
            "url": {
              "raw": "{{baseUrl}}/v1/cities/:code",
              "variable": [
                {
                  "key": "code",
                  "value": "EG",
                  "description": "ISO 3166-1 alpha-2 or alpha-3 code, case-insensitive"
                }
              ],
              "query": [
                {
                  "key": "search",
                  "value": "alex",
                  "description": "Partial city name filter — case-insensitive. Matches any city whose name contains this string."
                },
                {
                  "key": "limit",
                  "value": "20",
                  "description": "Max cities to return. Default: 20"
                },
                {
                  "key": "skip",
                  "value": "0",
                  "description": "Pagination offset. Default: 0"
                }
              ]
            },
            "header": []
          }
        },
        {
          "name": "❌ Get Cities — Country Not Found (404)",
          "description": "Demonstrates the 404 error when the country code is valid format but has no matching country.\n\n**Access:** Public\n**Returns:** 404 + `{ error: { code: 'NOT_FOUND', message: 'No country found for code \"XX\".' } }`",
          "request": {
            "method": "GET",
            "url": {
              "raw": "{{baseUrl}}/v1/cities/:code",
              "variable": [
                {
                  "key": "code",
                  "value": "XX",
                  "description": "Valid format but unrecognized country code — guaranteed 404"
                }
              ],
              "query": []
            },
            "header": []
          }
        },
        {
          "name": "❌ Get Cities — Bad Request (400)",
          "description": "Demonstrates the 400 error when the country code fails the format validation rule.\n\n**Access:** Public\n**Returns:** 400 + `{ error: { code: 'BAD_REQUEST', message: 'Invalid country code \"12345\"...' } }`",
          "request": {
            "method": "GET",
            "url": {
              "raw": "{{baseUrl}}/v1/cities/:code",
              "variable": [
                {
                  "key": "code",
                  "value": "12345",
                  "description": "Invalid code — not a 2–3 letter alpha string — guaranteed 400"
                }
              ],
              "query": []
            },
            "header": []
          }
        }
      ]
    }
  ]
}
