{
  "openapi": "3.1.0",
  "info": {
    "title": "N-Ways Maps Platform — Maps, Places and Routes API",
    "version": "1.0.0",
    "summary": "Maps, geocoding, postcodes, places, autocomplete, address validation, routes, route matrices, stop optimisation, fares and traffic for every country N-Ways covers (GET /v1/coverage lists them).",
    "description": "The stable partner surface of the N-Ways Maps Platform (`/maps/v1`). It is the source of truth for the\ndeveloper docs and the client libraries; the server's golden-file contract tests\n(backend/internal/partnerapi/testdata/golden) pin every answer described here.\n\n**Auth.** Every call needs a partner API key: `X-NWays-Api-Key: nw_…` (servers, apps) or `?key=nw_…`\n(browsers, map styles). A key's API restrictions name products; its application restriction (websites,\niOS bundle ids, Android package + SHA-1, server IP addresses) says where it may be used. Apps send\n`X-Ios-Bundle-Identifier`, or `X-Android-Package` and `X-Android-Cert`. Keys work only while N-Ways has the\nplatform switched on: while it is off every call answers `503 PLATFORM_DISABLED` (with `Retry-After`; your key\nis fine), and while N-Ways keeps it to invited organisations, a key of any other organisation answers\n`403 PLATFORM_PREVIEW_ONLY`.\n\n**Products and billing (SKUs).** `tiles` (map tiles; SKUs `tiles` per tile and `map_loads` per style read),\n`static_maps`, `geocode` (geocoding, reverse, postcodes, GhanaPost, Plus Codes; a batch counts each code),\n`autocomplete` (billed per session), `address_validation`, `search` (places; SKUs `search` basic,\n`places_contact`, `places_atmosphere` by the fields asked for), `routing` (SKUs `routing`, and\n`routing_advanced` for traffic-aware routes, okada/keke/danfo/motorbike/van/boat/train, more than 10\nintermediates or fares), `route_matrix` (billed per element), `route_optimization`, `fares`, `traffic`.\nFonts are free with any Maps key.\n\n**Conventions.** Coordinates go in as `{\"lat\": 6.45, \"lng\": 3.39}` (or `lat,lng` in a query) and come out\nas GeoJSON `[lng, lat]`. Distances in metres, durations in seconds, money in the currency's main unit.\n`lang=en|fr|pt` (pt is European Portuguese). `fields=` (a comma list; `a.b` reaches into objects) keeps only those fields of each result\nand, for places, sets the SKU. Fields are only ever added within v1; breaking changes come as `/maps/v2`\nwith 12 months' overlap.\n\n**Errors.** One envelope for key, quota and product errors:\n`{\"error\": {\"code\": 400, \"status\": \"INVALID_ARGUMENT\", \"reason\": \"LAT_LNG_REQUIRED\", \"message\": \"…\", \"details\": […]}}`.\n`reason` is stable (act on it); `message` says exactly what happened and how to fix it; `details` holds Google's\n`ErrorInfo` (the reason, `domain: nways.ng`, and `metadata` with the facts: `docs`, and per reason what was\nseen and what's allowed, which cap and when it resets) and a `Help` link to the reason's entry on\nhttps://nways.ng/developers/errors. Refusals carry CORS headers for the calling page, so browser code can\nread them. Key, platform and quota reasons, in the order they are checked: PLATFORM_DISABLED (503),\nAPI_KEY_MISSING, API_KEY_INVALID (a key N-Ways never issued), API_KEY_REVOKED (deleted, or the old value of a\nregenerated key) (401); PLATFORM_PREVIEW_ONLY, ORGANISATION_CLOSED, ORGANISATION_SUSPENDED, API_ACCESS_OFF (the organisation runs its deliveries on N-Ways without the API), API_TURNED_OFF (N-Ways turned the organisation's API off; the message gives the reason), PROJECT_SUSPENDED,\nAPI_KEY_PAUSED, API_KEY_SERVICE_BLOCKED, API_KEY_HTTP_REFERRER_BLOCKED, API_KEY_IP_ADDRESS_BLOCKED,\nAPI_KEY_IOS_APP_BLOCKED, API_KEY_ANDROID_APP_BLOCKED (403); RATE_LIMIT_EXCEEDED, QUOTA_EXCEEDED,\nTEMPORARILY_BLOCKED (429); BILLING_DISABLED (402: the free monthly allowance is used up and there's no credit\nleft: add credit in the console, Billing); BACKEND_ERROR (503). Product reasons are listed with each operation. Any operation may also answer\nNOT_FOUND (an unknown path), INVALID_JSON and BODY_TOO_LARGE (a request body), INTERNAL (500: send the\nX-Request-Id) and, where routes are planned, ROUTING_UNAVAILABLE (503, with Retry-After). Okada, motorbike, keke\nand danfo trips the law keeps off answer 422 with OKADA_BANNED_ORIGIN, OKADA_BANNED_DESTINATION, OKADA_BANNED_STOP, OKADA_BANNED_ROUTE; MOTORBIKE_BANNED_ORIGIN, MOTORBIKE_BANNED_DESTINATION, MOTORBIKE_BANNED_STOP, MOTORBIKE_BANNED_ROUTE; KEKE_BANNED_ORIGIN, KEKE_BANNED_DESTINATION, KEKE_BANNED_STOP, KEKE_BANNED_ROUTE; DANFO_BANNED_ORIGIN, DANFO_BANNED_DESTINATION, DANFO_BANNED_STOP, DANFO_BANNED_ROUTE\n(the ban is in the message).\n\n**Headers.** Every answer carries `X-Request-Id` (send it to support). Allowed calls carry\n`X-RateLimit-Limit`, `X-RateLimit-Remaining` and `X-RateLimit-Reset` (seconds); a 429 and PLATFORM_DISABLED\ncarry `Retry-After`.\n\n**Hosts.** `api.nways.ng/maps/v1` for everything. Hosted styles read the PMTiles archives and fonts from the CDN\n`tiles.nways.ng/maps/v1` (`/tiles/{tileset}.pmtiles`, `/tiles/countries.json`, `/fonts/…`, same paths): a public copy, with no key there\nand nothing counted; your key is checked and the map load counted when the style is read. One-tile `.mvt` URLs\nstay on the API, keyed. When the routing engine is down,\nroutes, matrices and optimisation answer `503 ROUTING_UNAVAILABLE` with `Retry-After`: send the same request again.\n\n**Rate limits** (defaults per key and minute): tiles 6,000, static_maps 600, geocode 600,\nautocomplete 1,200, address_validation 300, search 600, routing 300, route_matrix 3,000 elements,\nroute_optimization 60, fares 120, traffic 60.\n\n**Attribution.** Answers built on OpenStreetMap carry `attribution`; show \"© OpenStreetMap contributors\"\nwherever you show the data (the ODbL requires it). See https://nways.ng/developers/terms.\n",
    "contact": {
      "name": "N-Ways",
      "email": "hello@nways.ng",
      "url": "https://nways.ng/developers"
    }
  },
  "servers": [
    {
      "url": "https://api.nways.ng/maps/v1",
      "description": "Partner API host"
    },
    {
      "url": "https://nways.ng/maps/v1",
      "description": "Same API through the website's host"
    }
  ],
  "security": [
    {
      "apiKeyHeader": []
    },
    {
      "apiKeyQuery": []
    }
  ],
  "tags": [
    {
      "name": "Maps",
      "description": "Hosted styles, vector tiles, fonts and static map images."
    },
    {
      "name": "Geocoding",
      "description": "Search for places and addresses, what's at a point, and NIPOST, GhanaPost and Plus Code lookups."
    },
    {
      "name": "Autocomplete"
    },
    {
      "name": "Address validation"
    },
    {
      "name": "Places"
    },
    {
      "name": "Routes"
    },
    {
      "name": "Fares"
    },
    {
      "name": "Traffic"
    }
  ],
  "paths": {
    "/styles/{style}.json": {
      "get": {
        "tags": [
          "Maps"
        ],
        "operationId": "getStyle",
        "summary": "A hosted map style (one map load)",
        "description": "A MapLibre style for the N-Ways street map with the key on every N-Ways URL (tiles, fonts). Point MapLibre GL JS\nor MapLibre Native at it: `style: 'https://nways.ng/maps/v1/styles/streets-light.json?key=nw_…'`. Each read is\none map load (SKU `map_loads`, product `tiles`). Tiles come one at a time (`/tiles/base/{z}/{x}/{y}.mvt`, every\ncountry, with `bounds` around all of them), or from each country's PMTiles archive with `format=pmtiles` (needs\nthe pmtiles protocol, as the web SDK registers): then the style has one source per country (`nways-ng`,\n`nways-gh`, …) and each layer once per country (`roads-ng`, `roads-gh`, …).\nReasons: STYLE_NOT_FOUND, LANG_INVALID, FORMAT_INVALID.\n",
        "parameters": [
          {
            "name": "style",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "enum": [
                "streets-light",
                "streets-dark"
              ]
            }
          },
          {
            "$ref": "#/components/parameters/Lang"
          },
          {
            "name": "format",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "mvt",
                "pmtiles"
              ],
              "default": "mvt"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "A MapLibre style (version 8)",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "description": "MapLibre style specification v8: version, name, glyphs, sources (nways, or nways-<country> with format=pmtiles), layers."
                }
              }
            }
          },
          "default": {
            "$ref": "#/components/responses/Error"
          }
        }
      }
    },
    "/tiles/countries.json": {
      "get": {
        "tags": [
          "Maps"
        ],
        "operationId": "getTileCountries",
        "summary": "Every country's tile archives",
        "description": "The countries N-Ways maps today, each with its ISO code, box (`bbox`: west, south, east, north), outline (GeoJSON\nMultiPolygon coordinates) and its PMTiles archives (`files.base`, `files.detail`, `files.transit`; a kind may be\nmissing while a country is being built). Read a tile from the archive of the country whose outline holds the\ntile's centre, then any other whose box it touches. Only the archives listed here are served (any other name is\nTILESET_NOT_FOUND), so read the list again rather than keeping file names. Free with any Maps key (counted,\nnever charged). TILES_UNAVAILABLE when the server has no map data.\n",
        "responses": {
          "200": {
            "description": "The list",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "type": "object",
                    "required": [
                      "name",
                      "country",
                      "bbox",
                      "files"
                    ],
                    "properties": {
                      "name": {
                        "type": "string",
                        "example": "togo"
                      },
                      "country": {
                        "type": "string",
                        "example": "TG"
                      },
                      "detail": {
                        "type": "string",
                        "enum": [
                          "cities",
                          "country"
                        ]
                      },
                      "bbox": {
                        "type": "array",
                        "items": {
                          "type": "number"
                        },
                        "minItems": 4,
                        "maxItems": 4
                      },
                      "files": {
                        "type": "object",
                        "properties": {
                          "base": {
                            "type": "string",
                            "example": "togo.pmtiles"
                          },
                          "detail": {
                            "type": "string",
                            "example": "togo-detail.pmtiles"
                          },
                          "transit": {
                            "type": "string",
                            "example": "togo-transit.pmtiles"
                          }
                        }
                      },
                      "outline": {
                        "type": "array",
                        "items": {
                          "type": "array"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "default": {
            "$ref": "#/components/responses/Error"
          }
        }
      }
    },
    "/tiles/{tileset}.pmtiles": {
      "get": {
        "tags": [
          "Maps"
        ],
        "operationId": "getTileArchive",
        "summary": "Read a country's PMTiles archive (range requests)",
        "description": "One country's vector tiles as a PMTiles v3 archive: `<country>.pmtiles` (Protomaps basemap schema, zoom 0–15),\n`<country>-detail.pmtiles` (zoom 12–16) or `<country>-transit.pmtiles` (zoom 5–15), as listed in\n`/tiles/countries.json`, read in pieces with `Range: bytes=a-b` (at most 8 MB a read; the whole archive is never\nserved). The key is checked on the first read of an archive session from a site (every archive) and counted as\none `tiles` call; the next reads within 2 minutes ride on that check. Reasons: TILESET_NOT_FOUND, RANGE_REQUIRED,\nRANGE_TOO_LARGE, TILES_UNAVAILABLE.\n",
        "parameters": [
          {
            "name": "tileset",
            "in": "path",
            "required": true,
            "description": "A file name from /tiles/countries.json, without .pmtiles",
            "schema": {
              "type": "string",
              "example": "COUNTRY"
            }
          },
          {
            "name": "Range",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string",
              "example": "bytes=0-16383"
            }
          }
        ],
        "responses": {
          "206": {
            "description": "The bytes asked for",
            "content": {
              "application/vnd.pmtiles": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            }
          },
          "default": {
            "$ref": "#/components/responses/Error"
          }
        }
      }
    },
    "/tiles/{tileset}/{z}/{x}/{y}.mvt": {
      "get": {
        "tags": [
          "Maps"
        ],
        "operationId": "getTile",
        "summary": "One vector tile",
        "description": "A Mapbox Vector Tile (one `tiles` call) from the archive of the country it lies in, so every country is in every\ntileset: `base` (zoom 0–15), `detail` (12–16) or `transit` (5–15). Gzipped when the client accepts it. 204 where\nthere's no tile (open sea). Beyond the top zoom, overzoom (the style's source says `maxzoom: 15`). Reasons:\nTILESET_NOT_FOUND, TILE_INVALID, TILE_NOT_FOUND, TILES_UNAVAILABLE.\n",
        "parameters": [
          {
            "name": "tileset",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "enum": [
                "base",
                "detail",
                "transit"
              ]
            }
          },
          {
            "name": "z",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer",
              "minimum": 0,
              "maximum": 16
            }
          },
          {
            "name": "x",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer",
              "minimum": 0
            }
          },
          {
            "name": "y",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer",
              "minimum": 0
            }
          }
        ],
        "responses": {
          "200": {
            "description": "A tile",
            "content": {
              "application/vnd.mapbox-vector-tile": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            }
          },
          "204": {
            "description": "No tile here"
          },
          "default": {
            "$ref": "#/components/responses/Error"
          }
        }
      }
    },
    "/fonts/{stack}/{range}.pbf": {
      "get": {
        "tags": [
          "Maps"
        ],
        "operationId": "getGlyphs",
        "summary": "Font glyphs for map labels (free)",
        "description": "Glyph ranges for the style's fonts (Inter-Regular, Inter-Medium, Inter-SemiBold, Inter-Bold, Inter-Italic). Counted, never charged. Reasons: FONT_RANGE_INVALID, FONT_NOT_FOUND.",
        "parameters": [
          {
            "name": "stack",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "example": "Inter-Medium"
            }
          },
          {
            "name": "range",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "example": "0-255"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Glyphs",
            "content": {
              "application/x-protobuf": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            }
          },
          "default": {
            "$ref": "#/components/responses/Error"
          }
        }
      }
    },
    "/staticmap": {
      "get": {
        "tags": [
          "Maps"
        ],
        "operationId": "getStaticMap",
        "summary": "A map image",
        "description": "A PNG or JPEG of the map, drawn by N-Ways from its own tiles, with markers and lines. Give `center` and `zoom`, or\nonly markers and paths to frame them. One `static_maps` call.\n\n**Signed URLs.** Add `signature=` (last) to use a URL anywhere (an e-mail, an app with no Referer) whatever the\nkey's application restriction: the base64url HMAC-SHA256 of the URL's path and query (from `/maps/v1/staticmap?`,\nwithout `signature`) with the key's signing secret (base64url; N-Ways gives it to you with the key). A changed URL\nor a wrong signature: `403 SIGNATURE_INVALID`.\n\nReasons: SIZE_INVALID, SCALE_INVALID, THEME_INVALID, FORMAT_INVALID, MARKERS_INVALID, PATH_INVALID,\nCENTER_REQUIRED, CENTER_INVALID, ZOOM_INVALID, URL_TOO_LONG, SIGNATURE_INVALID.\n",
        "parameters": [
          {
            "name": "center",
            "in": "query",
            "schema": {
              "type": "string",
              "example": "6.4474,3.4720"
            },
            "description": "lat,lng"
          },
          {
            "name": "zoom",
            "in": "query",
            "schema": {
              "type": "number",
              "minimum": 0,
              "maximum": 20,
              "example": 15
            }
          },
          {
            "name": "size",
            "in": "query",
            "schema": {
              "type": "string",
              "default": "640x400",
              "example": "640x400"
            },
            "description": "WIDTHxHEIGHT, 32–1280 px each"
          },
          {
            "name": "scale",
            "in": "query",
            "schema": {
              "type": "integer",
              "enum": [
                1,
                2
              ],
              "default": 1
            }
          },
          {
            "name": "theme",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "light",
                "dark"
              ],
              "default": "light"
            }
          },
          {
            "name": "format",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "png",
                "jpg"
              ],
              "default": "png"
            }
          },
          {
            "name": "markers",
            "in": "query",
            "explode": true,
            "schema": {
              "type": "array",
              "items": {
                "type": "string"
              }
            },
            "description": "Repeatable: styles, then points, by `|`: `color:red|label:A|size:small|6.45,3.47|6.46,3.48`. Colours: red, green, blue, orange, yellow, purple, black, white, gray, or 0xRRGGBB[AA]. Sizes tiny, small, mid. At most 100 markers."
          },
          {
            "name": "path",
            "in": "query",
            "explode": true,
            "schema": {
              "type": "array",
              "items": {
                "type": "string"
              }
            },
            "description": "Repeatable: `color:0x1e6fe8cc|weight:5|fillcolor:0x…|6.45,3.47|6.46,3.48`, or an encoded polyline (precision 5) last: `weight:4|enc:_p~iF~ps|U…`. At most 10 paths, 2,000 points each."
          },
          {
            "name": "signature",
            "in": "query",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The image",
            "content": {
              "image/png": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              },
              "image/jpeg": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            }
          },
          "default": {
            "$ref": "#/components/responses/Error"
          }
        }
      }
    },
    "/geocode": {
      "get": {
        "tags": [
          "Geocoding"
        ],
        "operationId": "geocode",
        "summary": "Search for places, addresses and codes",
        "description": "Places, streets, addresses, landmarks, businesses and areas in every covered country, with typos and\nlocal nicknames; a NIPOST postcode finds its building, a GhanaPost GPS address its point. Nearer results first\nwhen `near` is given. Reasons: QUERY_REQUIRED, QUERY_TOO_LONG, NEAR_INVALID, COUNTRY_INVALID, LIMIT_INVALID,\nLANG_INVALID.\n",
        "parameters": [
          {
            "name": "q",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string",
              "maxLength": 200,
              "example": "Ikeja City Mall"
            }
          },
          {
            "$ref": "#/components/parameters/Near"
          },
          {
            "$ref": "#/components/parameters/Country"
          },
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 20,
              "default": 8
            }
          },
          {
            "$ref": "#/components/parameters/Lang"
          },
          {
            "$ref": "#/components/parameters/Fields"
          }
        ],
        "responses": {
          "200": {
            "description": "Results",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GeocodeResponse"
                }
              }
            }
          },
          "default": {
            "$ref": "#/components/responses/Error"
          }
        }
      }
    },
    "/reverse": {
      "get": {
        "tags": [
          "Geocoding"
        ],
        "operationId": "reverseGeocode",
        "summary": "What's at a point",
        "description": "One shape in every covered country: the country and region, the Plus Code, Nigeria's NIPOST postcode (the building\nwithin reach), Ghana's nearest known GhanaPost GPS address, and the nearest named address, street or landmark\n(else the area), with a one-line address. Reasons: LAT_LNG_REQUIRED, RADIUS_INVALID, LANG_INVALID.\n",
        "parameters": [
          {
            "name": "lat",
            "in": "query",
            "required": true,
            "schema": {
              "type": "number",
              "example": 6.4474
            }
          },
          {
            "name": "lng",
            "in": "query",
            "required": true,
            "schema": {
              "type": "number",
              "example": 3.472
            }
          },
          {
            "name": "radius",
            "in": "query",
            "schema": {
              "type": "number",
              "minimum": 10,
              "maximum": 1000,
              "default": 200
            },
            "description": "How far to look for an address, street or landmark (metres)."
          },
          {
            "$ref": "#/components/parameters/Lang"
          },
          {
            "$ref": "#/components/parameters/Fields"
          }
        ],
        "responses": {
          "200": {
            "description": "What's there",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ReverseResponse"
                }
              }
            }
          },
          "default": {
            "$ref": "#/components/responses/Error"
          }
        }
      }
    },
    "/postcodes/{code}": {
      "get": {
        "tags": [
          "Geocoding"
        ],
        "operationId": "getPostcode",
        "summary": "A NIPOST postcode",
        "description": "The building a NIPOST postcode names (any written form: `LA 08 A09 DB 09`, `la-08-a09-db-09`), its entrances\n(route to the first), its parts and their names; or, for a code we haven't placed yet, where it is estimated to\nbe from the codes around it (`estimated`, `accuracyM`). Reasons: POSTCODE_INVALID, POSTCODE_NOT_FOUND_CHECK,\nPOSTCODE_NOT_NIPOST.\n",
        "parameters": [
          {
            "name": "code",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "example": "LA08A09DB09"
            }
          },
          {
            "$ref": "#/components/parameters/Fields"
          }
        ],
        "responses": {
          "200": {
            "description": "The postcode",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Postcode"
                }
              }
            }
          },
          "default": {
            "$ref": "#/components/responses/Error"
          }
        }
      }
    },
    "/postcodes/batch": {
      "post": {
        "tags": [
          "Geocoding"
        ],
        "operationId": "getPostcodes",
        "summary": "Up to 100 NIPOST postcodes at once",
        "description": "Each code counts as one geocode call. An answer per code, in order, with its own error when it has one. Reasons (whole call) CODES_INVALID.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "codes"
                ],
                "properties": {
                  "codes": {
                    "type": "array",
                    "minItems": 1,
                    "maxItems": 100,
                    "items": {
                      "type": "string"
                    }
                  }
                }
              },
              "example": {
                "codes": [
                  "LA08A09DB09",
                  "FC 02 A09 DB 09"
                ]
              }
            }
          }
        },
        "parameters": [
          {
            "$ref": "#/components/parameters/Fields"
          }
        ],
        "responses": {
          "200": {
            "description": "One result per code",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "results",
                    "attribution"
                  ],
                  "properties": {
                    "results": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "required": [
                          "query"
                        ],
                        "properties": {
                          "query": {
                            "type": "string"
                          },
                          "postcode": {
                            "$ref": "#/components/schemas/Postcode"
                          },
                          "error": {
                            "$ref": "#/components/schemas/ItemError"
                          }
                        }
                      }
                    },
                    "attribution": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "default": {
            "$ref": "#/components/responses/Error"
          }
        }
      }
    },
    "/ghanapost/{code}": {
      "get": {
        "tags": [
          "Geocoding"
        ],
        "operationId": "getGhanaPost",
        "summary": "A GhanaPost GPS address",
        "description": "Where a GhanaPost GPS digital address is: mapped in OpenStreetMap or published by its owner, else (in districts\nwhere the method has been checked) estimated from the codes mapped around it (`estimated`, `accuracyM`). Never\ncalls Ghana Post. Reasons: GHANAPOST_INVALID, GHANAPOST_UNKNOWN.\n",
        "parameters": [
          {
            "name": "code",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "example": "GA-183-8164"
            }
          },
          {
            "$ref": "#/components/parameters/Fields"
          }
        ],
        "responses": {
          "200": {
            "description": "The address",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Result"
                }
              }
            }
          },
          "default": {
            "$ref": "#/components/responses/Error"
          }
        }
      }
    },
    "/pluscodes/{code}": {
      "get": {
        "tags": [
          "Geocoding"
        ],
        "operationId": "getPlusCode",
        "summary": "A Plus Code's area and what's there",
        "description": "A full Plus Code (`6FR5J975+P4W`), or a short one with a reference: `near=lat,lng`, or a town after it\n(`J975+P4 Ikeja`, URL-encoded). The answer is the reverse geocoding of the code's centre, with the full code and\nits bounds. Reasons: PLUSCODE_INVALID, PLUSCODE_SHORT, LOCALITY_NOT_FOUND, NEAR_INVALID.\n",
        "parameters": [
          {
            "name": "code",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "example": "6FR5J975+P4W"
            }
          },
          {
            "$ref": "#/components/parameters/Near"
          },
          {
            "$ref": "#/components/parameters/Lang"
          },
          {
            "$ref": "#/components/parameters/Fields"
          }
        ],
        "responses": {
          "200": {
            "description": "The code's area",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/ReverseResponse"
                    },
                    {
                      "type": "object",
                      "required": [
                        "bounds"
                      ],
                      "properties": {
                        "bounds": {
                          "type": "array",
                          "items": {
                            "type": "number"
                          },
                          "minItems": 4,
                          "maxItems": 4,
                          "description": "west, south, east, north"
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "default": {
            "$ref": "#/components/responses/Error"
          }
        }
      }
    },
    "/autocomplete": {
      "get": {
        "tags": [
          "Autocomplete"
        ],
        "operationId": "autocomplete",
        "summary": "Suggestions as someone types (billed per session)",
        "description": "Send the same `session` token (8–64 of `A–Z a–z 0–9 - _`, a UUID is fine) from the first letter until the user\npicks a result: the session's first request is billed (one `autocomplete` session); the rest are counted towards\nthe rate limit but not charged, for up to 3 minutes between requests, 15 minutes and 100 requests in all. Start a\nnew token after a pick. Without a token every request is a session. `X-NWays-Session-Billed: 1|0` says which.\nReasons: as /geocode, and SESSION_INVALID, TYPES_INVALID.\n",
        "parameters": [
          {
            "name": "q",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string",
              "example": "lekki ph"
            }
          },
          {
            "name": "session",
            "in": "query",
            "schema": {
              "type": "string",
              "example": "7d2c2b6e-5b7f-4f0e-9a51-2f4f3c1d9e10"
            }
          },
          {
            "$ref": "#/components/parameters/Near"
          },
          {
            "$ref": "#/components/parameters/Country"
          },
          {
            "name": "types",
            "in": "query",
            "schema": {
              "type": "string",
              "example": "address,business"
            },
            "description": "Comma list of address, street, business, place, area, postcode, ghanapost."
          },
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 10,
              "default": 5
            }
          },
          {
            "$ref": "#/components/parameters/Lang"
          },
          {
            "$ref": "#/components/parameters/Fields"
          }
        ],
        "responses": {
          "200": {
            "description": "Suggestions",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GeocodeResponse"
                }
              }
            }
          },
          "default": {
            "$ref": "#/components/responses/Error"
          }
        }
      }
    },
    "/addresses/validate": {
      "post": {
        "tags": [
          "Address validation"
        ],
        "operationId": "validateAddress",
        "summary": "Is this address real, where is it, what is it properly?",
        "description": "A NIPOST postcode, GhanaPost GPS address or Plus Code in the address is resolved first; the words are geocoded and\nchecked against the code; the components come from what is at the point, and a missing NIPOST postcode is filled\nin from the building there (`verdict.inferredPostcode`). Reasons: ADDRESS_REQUIRED, COUNTRY_INVALID, NEAR_INVALID,\nLANG_INVALID.\n",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ValidateRequest"
              },
              "example": {
                "address": "12 Admiralty Way, Lekki, LA 08 A09 DB 09",
                "country": "NG"
              }
            }
          }
        },
        "parameters": [
          {
            "$ref": "#/components/parameters/Fields"
          }
        ],
        "responses": {
          "200": {
            "description": "The verdict",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ValidateResponse"
                }
              }
            }
          },
          "default": {
            "$ref": "#/components/responses/Error"
          }
        }
      }
    },
    "/places/nearby": {
      "get": {
        "tags": [
          "Places"
        ],
        "operationId": "placesNearby",
        "summary": "Businesses and places near a point",
        "description": "By category (ranked by nearness and prominence, 40 a page; ATM and cash lists put paying machines first) and/or name.\nfields= sets the SKU: basic fields (default) `search`; any contact field `places_contact`; any atmosphere field\n`places_atmosphere`. Reasons: LAT_LNG_REQUIRED, CATEGORY_INVALID, CATEGORY_OR_QUERY_REQUIRED, RADIUS_INVALID,\nPAGE_TOKEN_INVALID, FIELDS_INVALID.\n",
        "parameters": [
          {
            "name": "lat",
            "in": "query",
            "required": true,
            "schema": {
              "type": "number",
              "example": 6.4474
            }
          },
          {
            "name": "lng",
            "in": "query",
            "required": true,
            "schema": {
              "type": "number",
              "example": 3.472
            }
          },
          {
            "name": "category",
            "in": "query",
            "schema": {
              "$ref": "#/components/schemas/Category"
            }
          },
          {
            "name": "q",
            "in": "query",
            "schema": {
              "type": "string",
              "maxLength": 60
            }
          },
          {
            "name": "radius",
            "in": "query",
            "schema": {
              "type": "number",
              "minimum": 200,
              "maximum": 50000
            }
          },
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 40,
              "default": 40
            }
          },
          {
            "name": "pageToken",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "$ref": "#/components/parameters/PlaceFields"
          }
        ],
        "responses": {
          "200": {
            "description": "Places",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PlacesResponse"
                }
              }
            }
          },
          "default": {
            "$ref": "#/components/responses/Error"
          }
        }
      }
    },
    "/places/search": {
      "get": {
        "tags": [
          "Places"
        ],
        "operationId": "placesSearch",
        "summary": "Businesses and places by text",
        "description": "\"Zenith Bank Ikeja\", \"pharmacy near Yaba\". SKUs as /places/nearby. Reasons: as /geocode, and RADIUS_INVALID, FIELDS_INVALID.",
        "parameters": [
          {
            "name": "q",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string",
              "example": "pharmacy Yaba"
            }
          },
          {
            "$ref": "#/components/parameters/Near"
          },
          {
            "name": "radius",
            "in": "query",
            "schema": {
              "type": "number",
              "minimum": 200,
              "maximum": 50000
            },
            "description": "With near."
          },
          {
            "$ref": "#/components/parameters/Country"
          },
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 20,
              "default": 10
            }
          },
          {
            "$ref": "#/components/parameters/Lang"
          },
          {
            "$ref": "#/components/parameters/PlaceFields"
          }
        ],
        "responses": {
          "200": {
            "description": "Places",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PlacesResponse"
                }
              }
            }
          },
          "default": {
            "$ref": "#/components/responses/Error"
          }
        }
      }
    },
    "/places/{id}": {
      "get": {
        "tags": [
          "Places"
        ],
        "operationId": "getPlace",
        "summary": "One place's details",
        "description": "`fields` is required: the fields you need (they set the SKU), or `*` for all (places_atmosphere). Reasons: PLACE_ID_INVALID, FIELDS_REQUIRED, FIELDS_INVALID, PLACE_NOT_FOUND.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "example": "osm:n2987189855"
            }
          },
          {
            "name": "fields",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string",
              "example": "name,location,phone,openingHours"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The place",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Place"
                }
              }
            }
          },
          "default": {
            "$ref": "#/components/responses/Error"
          }
        }
      }
    },
    "/routes/compute": {
      "post": {
        "tags": [
          "Routes"
        ],
        "operationId": "computeRoutes",
        "summary": "A route, with up to 25 intermediates",
        "description": "Drive, van, okada (motorbike taxi; keeps to okada bans, walks past a ban area's edge), motorbike (your own) and\ndispatch (couriers' bikes: exempt from passenger-okada bans),\nkeke and danfo (keep to their bans), walk, bicycle, boat (passenger boats and jetties) and train. Postcodes route\nto the building's best entrance; where the road stops short of the door the route says where to stop and how to\nfinish (`lastMile`); tolls in their currencies, border crossings, gated estates, bans avoided with their sources.\nfields= picks fields of each route. Reasons: MODE_INVALID, LANG_INVALID, ROUTING_PREFERENCE_INVALID,\nTOO_MANY_INTERMEDIATES, INTERMEDIATES_NOT_SUPPORTED, AVOID_INVALID, TOLL_VEHICLE_INVALID, LAST_MILE_INVALID,\nACCESS_INVALID, WAYPOINT_INVALID, POSTCODE_INVALID, POSTCODE_NOT_FOUND, GHANAPOST_INVALID, GHANAPOST_NOT_FOUND,\nPLUSCODE_INVALID, NO_ROUTE, NO_ROAD_ROUTE, ROUTE_TOO_FAR, ROUTING_UNAVAILABLE, OKADA_BANNED_ORIGIN, OKADA_BANNED_DESTINATION, OKADA_BANNED_ROUTE,\nKEKE_BANNED_*, DANFO_BANNED_*, MOTORBIKE_BANNED_*, ROUTING_UNAVAILABLE (503).\n",
        "parameters": [
          {
            "$ref": "#/components/parameters/Fields"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ComputeRequest"
              },
              "example": {
                "origin": {
                  "lat": 6.4474,
                  "lng": 3.472
                },
                "destination": {
                  "postcode": "LA08A09DB10"
                },
                "mode": "drive",
                "routingPreference": "traffic_aware"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Routes",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RoutesResponse"
                }
              }
            }
          },
          "default": {
            "$ref": "#/components/responses/Error"
          }
        }
      }
    },
    "/routes/matrix": {
      "post": {
        "tags": [
          "Routes"
        ],
        "operationId": "computeRouteMatrix",
        "summary": "Travel times from many origins to many destinations (per element)",
        "description": "Up to 50 origins and 50 destinations, 625 elements (origins × destinations), each billed as one `route_matrix`\nunit. Modes drive, van, walk, bicycle; okada, keke and danfo keep to their bans as /routes/compute does (an element\nfrom or to a ban area is NOT_ALLOWED, with the ban); motorbike and dispatch to bans on every motorcycle only. Points at most 400 km apart by car, 200 km otherwise. Reasons: MODE_INVALID, MATRIX_TOO_LARGE,\nMATRIX_TOO_FAR, WAYPOINT_INVALID, POSTCODE_INVALID, POSTCODE_NOT_FOUND, GHANAPOST_INVALID, GHANAPOST_NOT_FOUND, PLUSCODE_INVALID, ROUTING_UNAVAILABLE (503).\n",
        "parameters": [
          {
            "$ref": "#/components/parameters/Fields"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/MatrixRequest"
              },
              "example": {
                "origins": [
                  {
                    "lat": 6.4474,
                    "lng": 3.472
                  },
                  {
                    "postcode": "LA08A09DB09"
                  }
                ],
                "destinations": [
                  {
                    "lat": 6.43,
                    "lng": 3.42
                  },
                  {
                    "lat": 6.45,
                    "lng": 3.4
                  }
                ],
                "mode": "motorbike"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The matrix",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/MatrixResponse"
                }
              }
            }
          },
          "default": {
            "$ref": "#/components/responses/Error"
          }
        }
      }
    },
    "/routes/optimize": {
      "post": {
        "tags": [
          "Routes"
        ],
        "operationId": "optimizeRoutes",
        "summary": "Which vehicle visits which stops, in what order",
        "description": "Modes as /routes/matrix: stops inside a ban area for the mode are unassigned and listed in notAllowed.\nUp to 50 stops and 5 vehicles (start, optional end, capacity): the least total time within each vehicle's capacity,\nby a fast heuristic on the engine's travel times (not a guaranteed optimum). Each vehicle's route is planned like\n/routes/compute unless `geometry` is false. One `route_optimization` call. Reasons: MODE_INVALID, STOPS_INVALID,\nVEHICLES_INVALID, STOP_ID_INVALID, VEHICLE_ID_INVALID, STOP_INVALID, CAPACITY_INVALID, STOPS_TOO_FAR,\nWAYPOINT_INVALID, POSTCODE_INVALID, POSTCODE_NOT_FOUND, GHANAPOST_INVALID, GHANAPOST_NOT_FOUND, PLUSCODE_INVALID, LANG_INVALID, ROUTING_UNAVAILABLE (503).\n",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/OptimizeRequest"
              },
              "example": {
                "stops": [
                  {
                    "id": "order-1",
                    "location": {
                      "lat": 6.44,
                      "lng": 3.4
                    },
                    "serviceS": 120
                  },
                  {
                    "id": "order-2",
                    "location": {
                      "postcode": "LA08A09DB09"
                    },
                    "demand": 2
                  }
                ],
                "vehicles": [
                  {
                    "id": "bike-1",
                    "start": {
                      "lat": 6.4474,
                      "lng": 3.472
                    },
                    "capacity": 6
                  }
                ],
                "mode": "motorbike"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The plan",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OptimizeResponse"
                }
              }
            }
          },
          "default": {
            "$ref": "#/components/responses/Error"
          }
        }
      }
    },
    "/fares/estimate": {
      "get": {
        "tags": [
          "Fares"
        ],
        "operationId": "estimateFares",
        "summary": "What a ride costs",
        "description": "Taxi, shared taxi, okada, keke, danfo/bus, ride-hail and boat fares in NGN, GHS or XOF, from riders' reports\n(`basis: crowd`) or published sources (`basis: guide`), with night, rush hour, rain and charter. Modes the law\nkeeps off the trip are in `notAllowed`, with the rule's source. Pass the route's distanceM and durationS when you\nhave them. Reasons: FROM_TO_REQUIRED, MODE_INVALID, AT_INVALID, DISTANCEM_INVALID, DURATIONS_INVALID, LANG_INVALID.\n",
        "parameters": [
          {
            "name": "from",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string",
              "example": "6.4474,3.4720"
            }
          },
          {
            "name": "to",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string",
              "example": "6.5244,3.3792"
            }
          },
          {
            "name": "mode",
            "in": "query",
            "schema": {
              "type": "string",
              "example": "taxi,okada"
            },
            "description": "Comma list of taxi, shared_taxi, okada, keke, danfo, ridehail, boat (default all)."
          },
          {
            "name": "at",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "date-time"
            }
          },
          {
            "name": "distanceM",
            "in": "query",
            "schema": {
              "type": "number"
            }
          },
          {
            "name": "durationS",
            "in": "query",
            "schema": {
              "type": "number"
            }
          },
          {
            "name": "charter",
            "in": "query",
            "schema": {
              "type": "boolean"
            }
          },
          {
            "name": "rain",
            "in": "query",
            "schema": {
              "type": "boolean"
            }
          },
          {
            "$ref": "#/components/parameters/Lang"
          },
          {
            "$ref": "#/components/parameters/Fields"
          }
        ],
        "responses": {
          "200": {
            "description": "Estimates",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/FaresResponse"
                }
              }
            }
          },
          "default": {
            "$ref": "#/components/responses/Error"
          }
        }
      }
    },
    "/traffic": {
      "get": {
        "tags": [
          "Traffic"
        ],
        "operationId": "getTraffic",
        "summary": "Live traffic in an area",
        "description": "Roads with live readings from N-Ways' phones (anonymised), as GeoJSON lines with a congestion level. Reasons: BBOX_INVALID.",
        "parameters": [
          {
            "name": "bbox",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string",
              "example": "3.30,6.40,3.50,6.60"
            },
            "description": "minLng,minLat,maxLng,maxLat, under 3° across."
          }
        ],
        "responses": {
          "200": {
            "description": "Traffic",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TrafficResponse"
                }
              }
            }
          },
          "default": {
            "$ref": "#/components/responses/Error"
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "apiKeyHeader": {
        "type": "apiKey",
        "in": "header",
        "name": "X-NWays-Api-Key"
      },
      "apiKeyQuery": {
        "type": "apiKey",
        "in": "query",
        "name": "key"
      }
    },
    "parameters": {
      "Lang": {
        "name": "lang",
        "in": "query",
        "schema": {
          "type": "string",
          "enum": [
            "en",
            "fr",
            "pt"
          ],
          "default": "en"
        }
      },
      "Fields": {
        "name": "fields",
        "in": "query",
        "schema": {
          "type": "string",
          "example": "name,location"
        },
        "description": "Comma list of the fields to keep in each result (`a.b` reaches into objects; `id` is always kept; `*` = all)."
      },
      "PlaceFields": {
        "name": "fields",
        "in": "query",
        "schema": {
          "type": "string",
          "example": "name,location,phone"
        },
        "description": "Basic (SKU search): id, name, category, location, formattedAddress, plusCode, country, distanceM, businessStatus, attribution. Contact (places_contact): phone, email, website, openingHours. Atmosphere (places_atmosphere): rating, amenities, details. Default: basic."
      },
      "Near": {
        "name": "near",
        "in": "query",
        "schema": {
          "type": "string",
          "example": "6.5244,3.3792"
        },
        "description": "lat,lng: rank and pick results near it."
      },
      "Country": {
        "name": "country",
        "in": "query",
        "schema": {
          "type": "string",
          "example": "NG,GH"
        },
        "description": "Comma list of NG, GH, BJ, TG, GA, CI, LR, GN, SL, GW, SN, CM, BF, CV, GM."
      }
    },
    "responses": {
      "Error": {
        "description": "Error",
        "headers": {
          "X-Request-Id": {
            "schema": {
              "type": "string"
            }
          },
          "Retry-After": {
            "schema": {
              "type": "integer"
            },
            "description": "On 429 and PLATFORM_DISABLED (503): seconds to wait."
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      }
    },
    "schemas": {
      "Error": {
        "type": "object",
        "required": [
          "error"
        ],
        "properties": {
          "error": {
            "type": "object",
            "required": [
              "code",
              "status",
              "reason",
              "message",
              "details"
            ],
            "properties": {
              "code": {
                "type": "integer",
                "example": 400
              },
              "status": {
                "type": "string",
                "enum": [
                  "INVALID_ARGUMENT",
                  "UNAUTHENTICATED",
                  "PAYMENT_REQUIRED",
                  "PERMISSION_DENIED",
                  "NOT_FOUND",
                  "ABORTED",
                  "FAILED_PRECONDITION",
                  "RESOURCE_EXHAUSTED",
                  "UNAVAILABLE",
                  "INTERNAL"
                ],
                "example": "INVALID_ARGUMENT"
              },
              "reason": {
                "type": "string",
                "example": "LAT_LNG_REQUIRED"
              },
              "message": {
                "type": "string",
                "description": "What happened and how to fix it, for people (may be reworded: act on reason)."
              },
              "details": {
                "type": "array",
                "description": "Google's ErrorInfo (the reason, domain nways.ng, metadata with the facts and the docs link) and Help (the link to the reason on the Errors page).",
                "items": {
                  "$ref": "#/components/schemas/ErrorDetail"
                }
              }
            }
          }
        }
      },
      "ErrorDetail": {
        "type": "object",
        "required": [
          "@type"
        ],
        "properties": {
          "@type": {
            "type": "string",
            "enum": [
              "type.googleapis.com/google.rpc.ErrorInfo",
              "type.googleapis.com/google.rpc.Help"
            ]
          },
          "reason": {
            "type": "string",
            "description": "ErrorInfo: the same as error.reason."
          },
          "domain": {
            "type": "string",
            "const": "nways.ng"
          },
          "metadata": {
            "type": "object",
            "description": "ErrorInfo: `docs` (the reason's entry on https://nways.ng/developers/errors) and, by reason: `keyValid`\n(PLATFORM_DISABLED), `keyPrefix`, `why` (deleted | regenerated) and `revokedAt` (API_KEY_REVOKED),\n`closedAt`, `until`, `suspendReason` (organisation and project refusals), `service` and `allowed`\n(API_KEY_SERVICE_BLOCKED), `seen` and `allowed` (application restrictions), `cap` (key_per_minute,\norganisation_per_minute, key_per_day, organisation_per_day), `limit` and `resetsAt` (429s),\n`blockedUntil` (TEMPORARILY_BLOCKED).\n",
            "required": [
              "docs"
            ],
            "properties": {
              "docs": {
                "type": "string",
                "format": "uri",
                "example": "https://nways.ng/developers/errors#api-key-paused"
              }
            },
            "additionalProperties": {
              "type": "string"
            }
          },
          "links": {
            "type": "array",
            "description": "Help: where to read about the reason.",
            "items": {
              "type": "object",
              "required": [
                "description",
                "url"
              ],
              "properties": {
                "description": {
                  "type": "string"
                },
                "url": {
                  "type": "string",
                  "format": "uri"
                }
              }
            }
          }
        }
      },
      "ItemError": {
        "type": "object",
        "required": [
          "code",
          "reason",
          "message"
        ],
        "properties": {
          "code": {
            "type": "integer"
          },
          "reason": {
            "type": "string"
          },
          "message": {
            "type": "string"
          }
        }
      },
      "LatLng": {
        "type": "object",
        "required": [
          "lat",
          "lng"
        ],
        "properties": {
          "lat": {
            "type": "number",
            "minimum": -90,
            "maximum": 90
          },
          "lng": {
            "type": "number",
            "minimum": -180,
            "maximum": 180
          }
        }
      },
      "Waypoint": {
        "type": "object",
        "description": "Exactly one of lat and lng, postcode (NIPOST: routes to its best entrance), ghanaPost, or plusCode (full, or short with a town after it).",
        "properties": {
          "lat": {
            "type": "number"
          },
          "lng": {
            "type": "number"
          },
          "postcode": {
            "type": "string",
            "example": "LA08A09DB09"
          },
          "ghanaPost": {
            "type": "string",
            "example": "GA-183-8164"
          },
          "plusCode": {
            "type": "string",
            "example": "6FR5J975+P4W"
          },
          "heading": {
            "type": "number",
            "description": "Origin only: direction of travel (degrees), so a moving vehicle doesn't start with a U-turn."
          }
        }
      },
      "Point": {
        "type": "object",
        "required": [
          "type",
          "coordinates"
        ],
        "properties": {
          "type": {
            "type": "string",
            "const": "Point"
          },
          "coordinates": {
            "type": "array",
            "items": {
              "type": "number"
            },
            "minItems": 2,
            "maxItems": 2,
            "description": "[lng, lat]"
          }
        }
      },
      "LineString": {
        "type": "object",
        "required": [
          "type",
          "coordinates"
        ],
        "properties": {
          "type": {
            "type": "string",
            "const": "LineString"
          },
          "coordinates": {
            "type": "array",
            "items": {
              "type": "array",
              "items": {
                "type": "number"
              },
              "minItems": 2,
              "maxItems": 2
            }
          }
        }
      },
      "Area": {
        "type": "object",
        "required": [
          "code",
          "name"
        ],
        "properties": {
          "code": {
            "type": "string",
            "example": "NG-LA",
            "description": "ISO 3166-1 alpha-2 (country) or 3166-2 (region)"
          },
          "name": {
            "type": "string",
            "example": "Lagos State"
          },
          "nameFr": {
            "type": "string"
          },
          "namePt": {
            "type": "string",
            "description": "Countries only: the name in Portuguese"
          }
        }
      },
      "Result": {
        "type": "object",
        "required": [
          "id",
          "name",
          "type",
          "location",
          "plusCode"
        ],
        "properties": {
          "id": {
            "type": "string",
            "example": "osm:w568367632"
          },
          "name": {
            "type": "string",
            "example": "Ikeja City Mall"
          },
          "type": {
            "type": "string",
            "description": "address, street, business, landmark, building, postcode, ghanapost, or an area: neighbourhood, suburb, village, town, city, state…",
            "example": "landmark"
          },
          "category": {
            "type": "string",
            "example": "shopping"
          },
          "location": {
            "$ref": "#/components/schemas/Point"
          },
          "bbox": {
            "type": "array",
            "items": {
              "type": "number"
            },
            "minItems": 4,
            "maxItems": 4,
            "description": "west, south, east, north (areas)"
          },
          "formattedAddress": {
            "type": "string",
            "example": "Ikeja, Lagos"
          },
          "postcode": {
            "type": "string",
            "description": "NIPOST postcode, compact",
            "example": "LA08A09DB09"
          },
          "ghanaPost": {
            "type": "string",
            "example": "GA-183-8164"
          },
          "plusCode": {
            "type": "string",
            "example": "6FR5J975+P4W"
          },
          "country": {
            "type": "string",
            "example": "NG"
          },
          "distanceM": {
            "type": "number",
            "description": "From near (or the point asked about)."
          },
          "estimated": {
            "type": "boolean",
            "description": "The point is an estimate, within accuracyM."
          },
          "accuracyM": {
            "type": "number"
          },
          "placeId": {
            "type": "string",
            "description": "A business: its id for /places/{id}."
          },
          "attribution": {
            "type": "string"
          }
        }
      },
      "GeocodeResponse": {
        "type": "object",
        "required": [
          "results",
          "attribution"
        ],
        "properties": {
          "results": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Result"
            }
          },
          "didYouMean": {
            "type": "string"
          },
          "attribution": {
            "type": "string"
          }
        }
      },
      "PostcodeComponents": {
        "type": "object",
        "required": [
          "state",
          "lga",
          "district",
          "area",
          "building"
        ],
        "properties": {
          "state": {
            "type": "string",
            "example": "LA"
          },
          "stateName": {
            "type": "string",
            "example": "Lagos"
          },
          "lga": {
            "type": "string",
            "example": "08"
          },
          "lgaName": {
            "type": "string",
            "example": "Eti-Osa"
          },
          "district": {
            "type": "string",
            "example": "A09"
          },
          "area": {
            "type": "string",
            "example": "DB"
          },
          "building": {
            "type": "string",
            "example": "09"
          }
        }
      },
      "Entrance": {
        "type": "object",
        "required": [
          "location",
          "kind",
          "verified"
        ],
        "properties": {
          "location": {
            "$ref": "#/components/schemas/Point"
          },
          "kind": {
            "type": "string",
            "enum": [
              "main",
              "gate",
              "pedestrian",
              "service"
            ]
          },
          "verified": {
            "type": "boolean"
          }
        }
      },
      "Postcode": {
        "type": "object",
        "required": [
          "code",
          "display",
          "components",
          "location",
          "entrances",
          "plusCode",
          "country",
          "source",
          "estimated",
          "attribution"
        ],
        "properties": {
          "code": {
            "type": "string",
            "example": "LA08A09DB09"
          },
          "display": {
            "type": "string",
            "example": "LA 08 A09 DB 09"
          },
          "components": {
            "$ref": "#/components/schemas/PostcodeComponents"
          },
          "location": {
            "$ref": "#/components/schemas/Point"
          },
          "name": {
            "type": "string"
          },
          "address": {
            "type": "string"
          },
          "buildingType": {
            "type": "string"
          },
          "entrances": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Entrance"
            },
            "description": "Verified first, main first: route to the first."
          },
          "plusCode": {
            "type": "string"
          },
          "country": {
            "type": "string",
            "const": "NG"
          },
          "source": {
            "type": "string",
            "enum": [
              "nipost",
              "osm",
              "user",
              "estimate"
            ]
          },
          "estimated": {
            "type": "boolean"
          },
          "accuracyM": {
            "type": "number"
          },
          "distanceM": {
            "type": "number",
            "description": "Reverse geocoding: from the point asked about."
          },
          "attribution": {
            "type": "string"
          }
        }
      },
      "ReverseResponse": {
        "type": "object",
        "required": [
          "location",
          "plusCode",
          "country",
          "region",
          "postcode",
          "ghanaPost",
          "nearest",
          "attribution"
        ],
        "properties": {
          "location": {
            "$ref": "#/components/schemas/Point"
          },
          "plusCode": {
            "type": "string"
          },
          "country": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/Area"
              },
              {
                "type": "null"
              }
            ]
          },
          "region": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/Area"
              },
              {
                "type": "null"
              }
            ]
          },
          "formattedAddress": {
            "type": "string",
            "example": "12 Admiralty Way, Lekki, Lagos State, Nigeria"
          },
          "postcode": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/Postcode"
              },
              {
                "type": "null"
              }
            ],
            "description": "Nigeria: the building's NIPOST postcode, when one is within reach."
          },
          "ghanaPost": {
            "type": [
              "string",
              "null"
            ],
            "description": "Ghana: the nearest known GhanaPost GPS address (within 100 m)."
          },
          "nearest": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/Result"
              },
              {
                "type": "null"
              }
            ]
          },
          "attribution": {
            "type": "string"
          }
        }
      },
      "Category": {
        "type": "string",
        "enum": [
          "petrol",
          "charging",
          "food",
          "fastfood",
          "cafe",
          "bar",
          "pos",
          "bank",
          "atm",
          "cash",
          "mechanic",
          "vulcanizer",
          "carwash",
          "police",
          "hospital",
          "pharmacy",
          "hotel",
          "supermarket",
          "shop",
          "market",
          "motorpark",
          "parking",
          "church",
          "mosque",
          "phones",
          "salon",
          "school",
          "office",
          "attraction",
          "estate",
          "residential",
          "government",
          "university"
        ]
      },
      "Place": {
        "type": "object",
        "required": [
          "id"
        ],
        "properties": {
          "id": {
            "type": "string",
            "example": "osm:n2987189855"
          },
          "name": {
            "type": "string",
            "example": "Shoprite"
          },
          "category": {
            "type": "string",
            "example": "supermarket"
          },
          "location": {
            "$ref": "#/components/schemas/Point"
          },
          "formattedAddress": {
            "type": "string"
          },
          "plusCode": {
            "type": "string"
          },
          "country": {
            "type": "string"
          },
          "distanceM": {
            "type": "number"
          },
          "businessStatus": {
            "type": "string",
            "enum": [
              "OPERATIONAL",
              "UNVERIFIED",
              "REPORTED_CLOSED"
            ]
          },
          "attribution": {
            "type": "string"
          },
          "phone": {
            "type": "string",
            "description": "Contact tier"
          },
          "email": {
            "type": "string",
            "description": "Contact tier"
          },
          "website": {
            "type": "string",
            "description": "Contact tier"
          },
          "openingHours": {
            "type": "object",
            "description": "Contact tier",
            "required": [
              "text",
              "source"
            ],
            "properties": {
              "text": {
                "type": "string",
                "example": "Mo-Su 09:00-21:00",
                "description": "OpenStreetMap opening_hours syntax"
              },
              "source": {
                "type": "string",
                "enum": [
                  "listed",
                  "owner",
                  "brand",
                  "usual"
                ]
              },
              "confirmed": {
                "type": "integer"
              }
            }
          },
          "rating": {
            "type": "object",
            "description": "Atmosphere tier",
            "properties": {
              "score": {
                "type": "number",
                "description": "1–5, smoothed"
              },
              "count": {
                "type": "integer"
              },
              "percentUp": {
                "type": "integer"
              },
              "reviewCount": {
                "type": "integer"
              }
            }
          },
          "amenities": {
            "type": "object",
            "additionalProperties": {
              "type": "string"
            },
            "description": "Atmosphere tier: what it offers, as OpenStreetMap tags (payment:*, wheelchair, delivery…)."
          },
          "details": {
            "type": "object",
            "description": "Atmosphere tier: per-industry details."
          }
        }
      },
      "PlacesResponse": {
        "type": "object",
        "required": [
          "places",
          "attribution"
        ],
        "properties": {
          "places": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Place"
            }
          },
          "nextPageToken": {
            "type": "string"
          },
          "attribution": {
            "type": "string"
          }
        }
      },
      "ValidateRequest": {
        "type": "object",
        "required": [
          "address"
        ],
        "properties": {
          "address": {
            "type": "string",
            "maxLength": 300
          },
          "country": {
            "type": "string",
            "enum": [
              "NG",
              "GH",
              "BJ",
              "TG",
              "GA",
              "CI",
              "LR",
              "GN",
              "SL",
              "GW",
              "SN",
              "CM",
              "BF",
              "CV",
              "GM"
            ]
          },
          "near": {
            "$ref": "#/components/schemas/LatLng"
          },
          "lang": {
            "type": "string",
            "enum": [
              "en",
              "fr",
              "pt"
            ]
          }
        }
      },
      "ValidateResponse": {
        "type": "object",
        "required": [
          "verdict",
          "formattedAddress",
          "components",
          "location",
          "postcode",
          "issues",
          "attribution"
        ],
        "properties": {
          "verdict": {
            "type": "object",
            "required": [
              "status",
              "granularity",
              "confidence",
              "codesAgree"
            ],
            "properties": {
              "status": {
                "type": "string",
                "enum": [
                  "VALID",
                  "VALID_WITH_CORRECTIONS",
                  "PARTIAL",
                  "UNRESOLVED",
                  "INVALID"
                ]
              },
              "granularity": {
                "type": "string",
                "enum": [
                  "PREMISE",
                  "POINT",
                  "ROUTE",
                  "NEIGHBOURHOOD",
                  "LOCALITY",
                  "REGION",
                  "NONE"
                ]
              },
              "confidence": {
                "type": "number",
                "minimum": 0,
                "maximum": 1
              },
              "codesAgree": {
                "type": [
                  "boolean",
                  "null"
                ]
              },
              "inferredPostcode": {
                "type": "boolean"
              }
            }
          },
          "formattedAddress": {
            "type": "string"
          },
          "components": {
            "type": "array",
            "items": {
              "type": "object",
              "required": [
                "type",
                "text",
                "source"
              ],
              "properties": {
                "type": {
                  "type": "string",
                  "enum": [
                    "postcode",
                    "ghanapost",
                    "pluscode",
                    "premise",
                    "street",
                    "neighbourhood",
                    "locality",
                    "lga",
                    "region",
                    "country"
                  ]
                },
                "text": {
                  "type": "string"
                },
                "code": {
                  "type": "string"
                },
                "source": {
                  "type": "string",
                  "enum": [
                    "input",
                    "corrected",
                    "inferred",
                    "unconfirmed"
                  ]
                }
              }
            }
          },
          "location": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/Point"
              },
              {
                "type": "null"
              }
            ]
          },
          "accuracyM": {
            "type": "number"
          },
          "postcode": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/Postcode"
              },
              {
                "type": "null"
              }
            ]
          },
          "plusCode": {
            "type": "string"
          },
          "issues": {
            "type": "array",
            "items": {
              "type": "object",
              "required": [
                "code",
                "message"
              ],
              "properties": {
                "code": {
                  "type": "string",
                  "example": "CODE_ADDRESS_MISMATCH",
                  "description": "POSTCODE_INVALID, POSTCODE_UNKNOWN, GHANAPOST_UNKNOWN, PLUSCODE_UNRESOLVED, LEGACY_POSTCODE, CODE_ADDRESS_MISMATCH, COUNTRY_MISMATCH, WORDS_NOT_FOUND, NOT_FOUND"
                },
                "message": {
                  "type": "string"
                }
              }
            }
          },
          "attribution": {
            "type": "string"
          }
        }
      },
      "Mode": {
        "type": "string",
        "enum": [
          "drive",
          "van",
          "okada",
          "motorbike",
          "dispatch",
          "keke",
          "danfo",
          "walk",
          "bicycle",
          "boat",
          "train"
        ],
        "default": "drive"
      },
      "ComputeRequest": {
        "type": "object",
        "required": [
          "origin",
          "destination"
        ],
        "properties": {
          "origin": {
            "$ref": "#/components/schemas/Waypoint"
          },
          "destination": {
            "$ref": "#/components/schemas/Waypoint"
          },
          "intermediates": {
            "type": "array",
            "maxItems": 25,
            "items": {
              "$ref": "#/components/schemas/Waypoint"
            }
          },
          "mode": {
            "$ref": "#/components/schemas/Mode"
          },
          "alternatives": {
            "type": "boolean",
            "default": false,
            "description": "Up to 2 more routes (no intermediates)."
          },
          "routingPreference": {
            "type": "string",
            "enum": [
              "traffic_unaware",
              "traffic_aware"
            ],
            "default": "traffic_unaware"
          },
          "departAt": {
            "type": "string",
            "format": "date-time"
          },
          "avoid": {
            "type": "array",
            "items": {
              "type": "string",
              "enum": [
                "tolls",
                "highways",
                "bad_roads"
              ]
            }
          },
          "lang": {
            "type": "string",
            "enum": [
              "en",
              "fr",
              "pt"
            ]
          },
          "tollVehicle": {
            "type": "string",
            "enum": [
              "car",
              "suv",
              "bus",
              "truck"
            ],
            "description": "Toll tariff class (van: bus)."
          },
          "access": {
            "type": "string",
            "enum": [
              "walk",
              "okada",
              "drive"
            ],
            "description": "Train and boat trips: to and from stations and jetties."
          },
          "lastMile": {
            "type": "string",
            "enum": [
              "walk",
              "okada"
            ]
          },
          "allowBans": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Unconfirmed ban ids (from unconfirmedBans) to route through anyway."
          },
          "fares": {
            "type": "boolean",
            "default": false,
            "description": "Add fare estimates to each route (advanced SKU)."
          }
        }
      },
      "Leg": {
        "type": "object",
        "required": [
          "distanceM",
          "durationS"
        ],
        "properties": {
          "distanceM": {
            "type": "number"
          },
          "durationS": {
            "type": "number"
          }
        }
      },
      "Step": {
        "type": "object",
        "required": [
          "maneuver",
          "instruction",
          "distanceM",
          "durationS",
          "startIndex",
          "endIndex"
        ],
        "properties": {
          "maneuver": {
            "type": "string",
            "example": "right",
            "description": "depart, arrive, continue, left, right, slight_left, sharp_right, uturn_left, roundabout_enter, ferry_enter, … (other)"
          },
          "instruction": {
            "type": "string",
            "example": "Turn right onto Admiralty Way."
          },
          "distanceM": {
            "type": "number"
          },
          "durationS": {
            "type": "number"
          },
          "streetNames": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "startIndex": {
            "type": "integer",
            "description": "Index into geometry.coordinates"
          },
          "endIndex": {
            "type": "integer"
          },
          "roundaboutExit": {
            "type": "integer"
          },
          "toward": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "atTrafficLight": {
            "type": "boolean"
          },
          "crossing": {
            "type": "string",
            "description": "On foot: footbridge, underpass, zebra, signals, crossing, junction"
          }
        }
      },
      "Ban": {
        "type": "object",
        "required": [
          "id",
          "zone",
          "scope"
        ],
        "properties": {
          "id": {
            "type": "string"
          },
          "zone": {
            "type": "string",
            "example": "Third Mainland Bridge"
          },
          "scope": {
            "type": "string"
          },
          "hours": {
            "type": "string"
          },
          "since": {
            "type": "string"
          },
          "source": {
            "type": "string",
            "format": "uri"
          },
          "where": {
            "type": "string",
            "enum": [
              "origin",
              "destination",
              "stop",
              "route"
            ]
          },
          "walkM": {
            "type": "number"
          }
        }
      },
      "Route": {
        "type": "object",
        "required": [
          "distanceM",
          "durationS",
          "staticDurationS",
          "trafficDelayS",
          "geometry",
          "legs",
          "steps"
        ],
        "properties": {
          "distanceM": {
            "type": "number"
          },
          "durationS": {
            "type": "number"
          },
          "staticDurationS": {
            "type": "number",
            "description": "Without live traffic"
          },
          "trafficDelayS": {
            "type": "number",
            "description": "What traffic and incidents add now (traffic_aware)."
          },
          "speedSource": {
            "type": "string",
            "enum": [
              "live",
              "typical",
              "estimated"
            ]
          },
          "summary": {
            "type": "string",
            "example": "Admiralty Way and Awudu Ekpekha Boulevard"
          },
          "geometry": {
            "$ref": "#/components/schemas/LineString"
          },
          "legs": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Leg"
            },
            "description": "One per part between stops."
          },
          "steps": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Step"
            }
          },
          "segments": {
            "type": "array",
            "description": "Trips that change how you travel (train, boat, okada with a walk past a ban).",
            "items": {
              "type": "object",
              "required": [
                "mode",
                "from",
                "to",
                "distanceM",
                "durationS",
                "geometry"
              ],
              "properties": {
                "mode": {
                  "type": "string",
                  "enum": [
                    "walk",
                    "okada",
                    "drive",
                    "train",
                    "boat"
                  ]
                },
                "line": {
                  "type": "string"
                },
                "from": {
                  "type": "string"
                },
                "to": {
                  "type": "string"
                },
                "distanceM": {
                  "type": "number"
                },
                "durationS": {
                  "type": "number"
                },
                "waitS": {
                  "type": "number"
                },
                "stops": {
                  "type": "integer"
                },
                "scheduled": {
                  "type": "boolean"
                },
                "estimated": {
                  "type": "boolean"
                },
                "fare": {
                  "type": "string"
                },
                "note": {
                  "type": "string"
                },
                "geometry": {
                  "$ref": "#/components/schemas/LineString"
                }
              }
            }
          },
          "congestion": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "startIndex": {
                  "type": "integer"
                },
                "endIndex": {
                  "type": "integer"
                },
                "level": {
                  "type": "string"
                }
              }
            }
          },
          "tolls": {
            "type": "object",
            "properties": {
              "gates": {
                "type": "array",
                "items": {
                  "type": "object",
                  "properties": {
                    "name": {
                      "type": "string"
                    },
                    "location": {
                      "$ref": "#/components/schemas/Point"
                    },
                    "atM": {
                      "type": "integer"
                    },
                    "price": {
                      "type": [
                        "integer",
                        "null"
                      ]
                    },
                    "currency": {
                      "type": "string"
                    },
                    "payment": {
                      "type": "string"
                    }
                  }
                }
              },
              "total": {
                "type": "object",
                "additionalProperties": {
                  "type": "integer"
                },
                "example": {
                  "NGN": 300
                }
              },
              "unknownPrices": {
                "type": "integer"
              }
            }
          },
          "borderCrossings": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "from": {
                  "type": "string"
                },
                "to": {
                  "type": "string"
                },
                "location": {
                  "$ref": "#/components/schemas/Point"
                },
                "atM": {
                  "type": "integer"
                },
                "post": {
                  "type": "string"
                },
                "clockChangeH": {
                  "type": "number"
                }
              }
            }
          },
          "vehicleRules": {
            "type": "object",
            "description": "okada, keke and danfo trips",
            "properties": {
              "vehicle": {
                "type": "string",
                "enum": [
                  "okada",
                  "motorbike",
                  "keke",
                  "danfo"
                ]
              },
              "avoided": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/Ban"
                }
              },
              "advice": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/Ban"
                }
              },
              "endBan": {
                "$ref": "#/components/schemas/Ban"
              },
              "allowedByRequest": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/Ban"
                }
              },
              "reported": {
                "type": "array",
                "items": {
                  "type": "object",
                  "properties": {
                    "location": {
                      "$ref": "#/components/schemas/Point"
                    },
                    "reporters": {
                      "type": "integer"
                    },
                    "lastSeen": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "lastMile": {
            "type": "object",
            "properties": {
              "stopAt": {
                "$ref": "#/components/schemas/Point"
              },
              "stopName": {
                "type": "string"
              },
              "mode": {
                "type": "string"
              },
              "distanceM": {
                "type": "number"
              },
              "durationS": {
                "type": "number"
              },
              "geometry": {
                "$ref": "#/components/schemas/LineString"
              }
            }
          },
          "access": {
            "type": "object",
            "properties": {
              "kind": {
                "type": "string",
                "enum": [
                  "estate",
                  "government",
                  "military",
                  "police",
                  "gate"
                ]
              },
              "text": {
                "type": "string"
              },
              "name": {
                "type": "string"
              },
              "gate": {
                "$ref": "#/components/schemas/Point"
              },
              "dropOffAtGate": {
                "type": "boolean"
              }
            }
          },
          "roadCondition": {
            "type": "object",
            "properties": {
              "roughM": {
                "type": "number"
              }
            }
          },
          "country": {
            "type": "string"
          },
          "warnings": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "fares": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/FareEstimate"
            }
          }
        }
      },
      "RoutesResponse": {
        "type": "object",
        "required": [
          "routes",
          "waypoints",
          "mode",
          "attribution"
        ],
        "properties": {
          "routes": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Route"
            }
          },
          "waypoints": {
            "type": "array",
            "description": "Where each waypoint was routed to, in order.",
            "items": {
              "type": "object",
              "required": [
                "location"
              ],
              "properties": {
                "location": {
                  "$ref": "#/components/schemas/Point"
                },
                "postcode": {
                  "type": "string"
                },
                "entrance": {
                  "type": "string"
                }
              }
            }
          },
          "mode": {
            "$ref": "#/components/schemas/Mode"
          },
          "warnings": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "unconfirmedBans": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Ban"
            }
          },
          "attribution": {
            "type": "string"
          }
        }
      },
      "MatrixRequest": {
        "type": "object",
        "required": [
          "origins",
          "destinations"
        ],
        "properties": {
          "origins": {
            "type": "array",
            "minItems": 1,
            "maxItems": 50,
            "items": {
              "$ref": "#/components/schemas/Waypoint"
            }
          },
          "destinations": {
            "type": "array",
            "minItems": 1,
            "maxItems": 50,
            "items": {
              "$ref": "#/components/schemas/Waypoint"
            }
          },
          "mode": {
            "$ref": "#/components/schemas/MatrixMode"
          },
          "allowBans": {
            "type": "array",
            "items": {
              "type": "string"
            }
          }
        }
      },
      "MatrixMode": {
        "type": "string",
        "enum": [
          "drive",
          "van",
          "okada",
          "motorbike",
          "dispatch",
          "keke",
          "danfo",
          "walk",
          "bicycle"
        ],
        "default": "drive",
        "description": "okada, keke and danfo keep to their bans (as /routes/compute); motorbike and dispatch (couriers' bikes) only to bans on every motorcycle."
      },
      "MatrixResponse": {
        "type": "object",
        "required": [
          "elements",
          "origins",
          "destinations",
          "mode",
          "attribution"
        ],
        "properties": {
          "elements": {
            "type": "array",
            "items": {
              "type": "object",
              "required": [
                "originIndex",
                "destinationIndex",
                "status",
                "durationS",
                "distanceM"
              ],
              "properties": {
                "originIndex": {
                  "type": "integer"
                },
                "destinationIndex": {
                  "type": "integer"
                },
                "status": {
                  "type": "string",
                  "enum": [
                    "OK",
                    "ROUTE_NOT_FOUND",
                    "NOT_ALLOWED"
                  ],
                  "description": "NOT_ALLOWED: the origin or destination is inside a ban area for the mode (ban says which)."
                },
                "durationS": {
                  "type": "number"
                },
                "distanceM": {
                  "type": "number"
                },
                "ban": {
                  "$ref": "#/components/schemas/Ban"
                }
              }
            }
          },
          "origins": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Point"
            }
          },
          "destinations": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Point"
            }
          },
          "mode": {
            "type": "string"
          },
          "attribution": {
            "type": "string"
          }
        }
      },
      "OptimizeRequest": {
        "type": "object",
        "required": [
          "stops",
          "vehicles"
        ],
        "properties": {
          "stops": {
            "type": "array",
            "minItems": 1,
            "maxItems": 50,
            "items": {
              "type": "object",
              "required": [
                "id",
                "location"
              ],
              "properties": {
                "id": {
                  "type": "string",
                  "pattern": "^[A-Za-z0-9_.:-]{1,64}$"
                },
                "location": {
                  "$ref": "#/components/schemas/Waypoint"
                },
                "demand": {
                  "type": "integer",
                  "minimum": 1,
                  "default": 1
                },
                "serviceS": {
                  "type": "number",
                  "minimum": 0,
                  "maximum": 14400,
                  "default": 0
                }
              }
            }
          },
          "vehicles": {
            "type": "array",
            "minItems": 1,
            "maxItems": 5,
            "items": {
              "type": "object",
              "required": [
                "id",
                "start"
              ],
              "properties": {
                "id": {
                  "type": "string",
                  "pattern": "^[A-Za-z0-9_.:-]{1,64}$"
                },
                "start": {
                  "$ref": "#/components/schemas/Waypoint"
                },
                "end": {
                  "$ref": "#/components/schemas/Waypoint"
                },
                "capacity": {
                  "type": "integer",
                  "minimum": 0,
                  "default": 0,
                  "description": "0 = no limit"
                }
              }
            }
          },
          "mode": {
            "$ref": "#/components/schemas/MatrixMode"
          },
          "allowBans": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "geometry": {
            "type": "boolean",
            "default": true
          },
          "lang": {
            "type": "string",
            "enum": [
              "en",
              "fr",
              "pt"
            ]
          }
        }
      },
      "OptimizeResponse": {
        "type": "object",
        "required": [
          "routes",
          "unassigned",
          "durationS",
          "mode",
          "attribution"
        ],
        "properties": {
          "routes": {
            "type": "array",
            "items": {
              "type": "object",
              "required": [
                "vehicle",
                "stops",
                "durationS",
                "distanceM",
                "serviceS",
                "load",
                "arrivalsS"
              ],
              "properties": {
                "vehicle": {
                  "type": "string"
                },
                "stops": {
                  "type": "array",
                  "items": {
                    "type": "string"
                  },
                  "description": "Stop ids in visiting order"
                },
                "durationS": {
                  "type": "number"
                },
                "distanceM": {
                  "type": "number"
                },
                "serviceS": {
                  "type": "number"
                },
                "load": {
                  "type": "integer"
                },
                "arrivalsS": {
                  "type": "array",
                  "items": {
                    "type": "number"
                  },
                  "description": "Seconds from the start to each stop"
                },
                "route": {
                  "$ref": "#/components/schemas/Route"
                }
              }
            }
          },
          "unassigned": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "notAllowed": {
            "type": "array",
            "description": "Stops, or vehicles' starts and ends, inside a ban area for the mode.",
            "items": {
              "type": "object",
              "required": [
                "ban"
              ],
              "properties": {
                "stop": {
                  "type": "string"
                },
                "vehicle": {
                  "type": "string"
                },
                "ban": {
                  "$ref": "#/components/schemas/Ban"
                }
              }
            }
          },
          "durationS": {
            "type": "number"
          },
          "mode": {
            "type": "string"
          },
          "attribution": {
            "type": "string"
          }
        }
      },
      "FareEstimate": {
        "type": "object",
        "required": [
          "mode",
          "currency",
          "low",
          "typical",
          "high",
          "text",
          "perPerson",
          "charter",
          "basis",
          "reports",
          "durationS"
        ],
        "properties": {
          "mode": {
            "type": "string",
            "enum": [
              "taxi",
              "shared_taxi",
              "okada",
              "keke",
              "bus",
              "ridehail",
              "boat"
            ]
          },
          "service": {
            "type": "string",
            "example": "Bolt"
          },
          "currency": {
            "type": "string",
            "enum": [
              "NGN",
              "GHS",
              "XOF"
            ]
          },
          "low": {
            "type": "integer"
          },
          "typical": {
            "type": "integer"
          },
          "high": {
            "type": "integer"
          },
          "text": {
            "type": "string",
            "example": "₦3,000–4,500"
          },
          "perPerson": {
            "type": "boolean"
          },
          "charter": {
            "type": "boolean"
          },
          "night": {
            "type": "boolean"
          },
          "rush": {
            "type": "boolean"
          },
          "rain": {
            "type": "boolean"
          },
          "basis": {
            "type": "string",
            "enum": [
              "crowd",
              "guide"
            ]
          },
          "reports": {
            "type": "integer"
          },
          "durationS": {
            "type": "number"
          },
          "openingOffer": {
            "type": "integer",
            "description": "Where prices are haggled: a good first offer."
          },
          "sources": {
            "type": "array",
            "items": {
              "type": "string",
              "format": "uri"
            }
          }
        }
      },
      "FaresResponse": {
        "type": "object",
        "required": [
          "country",
          "distanceM",
          "durationS",
          "night",
          "estimates",
          "notAllowed",
          "attribution"
        ],
        "properties": {
          "country": {
            "type": "string"
          },
          "region": {
            "type": "string"
          },
          "currency": {
            "type": "string"
          },
          "distanceM": {
            "type": "number"
          },
          "durationS": {
            "type": "number"
          },
          "night": {
            "type": "boolean"
          },
          "estimates": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/FareEstimate"
            }
          },
          "notAllowed": {
            "type": "array",
            "items": {
              "type": "object",
              "required": [
                "mode",
                "ban",
                "text"
              ],
              "properties": {
                "mode": {
                  "type": "string"
                },
                "ban": {
                  "$ref": "#/components/schemas/Ban"
                },
                "text": {
                  "type": "string"
                }
              }
            }
          },
          "attribution": {
            "type": "string"
          }
        }
      },
      "TrafficResponse": {
        "type": "object",
        "required": [
          "type",
          "features",
          "attribution"
        ],
        "properties": {
          "type": {
            "type": "string",
            "const": "FeatureCollection"
          },
          "features": {
            "type": "array",
            "items": {
              "type": "object",
              "required": [
                "type",
                "geometry",
                "properties"
              ],
              "properties": {
                "type": {
                  "type": "string",
                  "const": "Feature"
                },
                "geometry": {
                  "$ref": "#/components/schemas/LineString"
                },
                "properties": {
                  "type": "object",
                  "required": [
                    "level",
                    "levelName",
                    "speedKmh",
                    "normalKmh",
                    "confidence"
                  ],
                  "properties": {
                    "level": {
                      "type": "integer",
                      "minimum": 0,
                      "maximum": 3
                    },
                    "levelName": {
                      "type": "string",
                      "enum": [
                        "free",
                        "slow",
                        "heavy",
                        "standstill"
                      ]
                    },
                    "speedKmh": {
                      "type": "number"
                    },
                    "normalKmh": {
                      "type": "number"
                    },
                    "confidence": {
                      "type": "string",
                      "enum": [
                        "low",
                        "medium",
                        "high"
                      ]
                    }
                  }
                }
              }
            }
          },
          "attribution": {
            "type": "string"
          }
        }
      }
    }
  }
}
