> ## Documentation Index
> Fetch the complete documentation index at: https://docs.nozlerouting.com/llms.txt
> Use this file to discover all available pages before exploring further.

# POST /v1/stations/enrich-distances

> Required preparation step before /v1/optimize. Takes a route and a list of stations, returns the same stations augmented with `miles_from_route_start` (how far along the route the station sits) and `detour_minutes` (how much extra driving time the detour adds).

Two filter stages eliminate stations that wouldn't be useful:

1. **Coordinate pre-filter** drops stations more than `coarse_filter_radius_miles` from the route. Cheap; no API calls. Default 7 miles, range 5-20.
2. **Detour threshold** drops stations whose round-trip detour exceeds `max_detour_minutes`. Default 10 minutes, range 1-20.

Partner-provided fields (`id`, `price_per_gallon`, `brand`, `metadata`, etc.) are preserved unchanged — only the two computed fields are added.

Returns warnings reporting how many stations were dropped at each stage.

The `/v1/stations/enrich-distances` endpoint takes a route and a list of stations, returning the same stations with `miles_from_route_start` and `detour_minutes` populated for each station that passes the filter thresholds.

## When to Use

Required before `/v1/optimize` if your station data doesn't already include route-relative distances. If you compute these values in your own systems, you can skip this endpoint and pass enriched stations directly to `/v1/optimize`.

## Filters

Two filters control which stations are returned:

* **Coarse radius filter** (`coarse_filter_radius_miles`, default 7): stations farther than this from the route polyline are dropped without matrix routing calls. Reduces cost.
* **Max detour filter** (`max_detour_minutes`, default 10): stations whose round-trip detour exceeds this threshold are dropped after matrix routing.

## Behavior

* Partner-provided IDs and metadata are preserved unchanged in the response
* Stations dropped by filters are excluded from the response (not returned with null values)
* The response includes a count of stations dropped at each filter stage for debugging


## OpenAPI

````yaml POST /v1/stations/enrich-distances
openapi: 3.1.0
info:
  title: Nozle B2B API
  description: >-
    Fuel-route optimization for fleet operators. Takes a route, your stations
    with prices, and a vehicle. Returns the cost-optimal stopping sequence with
    a baseline comparison.


    ## Data Model


    **Nozle is compute-only.** We don't maintain a station database or pricing
    feed — partners bring their own station data, and we do the routing math.
    Specifically:


    - **You provide:** stations with current prices, a vehicle, and trip
    endpoints (or a precomputed route).

    - **We provide:** route geometry, distance enrichment for your stations, the
    optimization algorithm, and a baseline comparison showing what naive
    refueling would cost.


    This separation matters because (a) you almost certainly have more accurate
    price data than any third-party feed could provide, and (b) it keeps the API
    stateless — no syncing, no staleness, no licensing constraints on station
    coverage.


    ## Concepts


    **Route** — A driving path between two points, expressed as total distance,
    total duration, and an encoded polyline (Google Polyline Algorithm Format).
    You can compute one with `/v1/routes` or pass in a route you already have
    from another provider.


    **Station** — A fueling location with coordinates, a price per gallon, and a
    fuel type. After enrichment, stations also carry `miles_from_route_start`
    (their position along the route) and `detour_minutes` (time cost to visit
    them). You bring stations; we annotate them.


    **Vehicle** — A fuel-efficiency profile. Either direct specs (`mpg`,
    `tank_capacity_gallons`, `current_fuel_gallons`) or a year/make/model
    lookup. Direct specs always win when complete; lookup is a convenience for
    partners who don't track tank size.


    **Optimization** — A dynamic-programming search for the stop sequence that
    minimizes `(fuel_cost + λ * detour_time)` while respecting tank capacity and
    a destination fuel reserve. The knob `time_value_dollars_per_minute`
    controls λ — higher values make the optimizer more time-sensitive (skip
    cheap-but-distant stations), lower values prioritize cost.


    ## Authentication


    All endpoints require an `X-API-Key` header. Email
    `rohan.iyer@nozlerouting.com` to request a key.


    ## Pipeline


    Most partners use the three endpoints in sequence:


    1. **`POST /v1/routes`** — compute a driving route between two addresses.

    2. **`POST /v1/stations/enrich-distances`** — annotate your stations with
    route-relative positions and detour times.

    3. **`POST /v1/optimize`** — compute the optimal fueling plan, with savings
    vs. naive baseline.


    Partners with their own routing can skip step 1 and pass a route directly
    into steps 2 and 3.


    ## Quickstart


    Minimal Python example calling all three endpoints. Requires `requests`.
    Replace `YOUR_API_KEY` with the key you received from support.


    **[Download the full example](/examples/quickstart.py)** (`curl
    /examples/quickstart.py > quickstart.py`) for a complete version with error
    handling, structured-error parsing, and curl equivalents in comments for
    partners using other languages.


    ```python

    import requests


    BASE = 'https://api.nozlerouting.com'

    HEADERS = {'X-API-Key': 'YOUR_API_KEY'}


    # 1. Compute a route

    route = requests.post(f'{BASE}/v1/routes', headers=HEADERS, json={
        'origin': 'San Francisco, CA',
        'destination': 'Los Angeles, CA',
    }).json()['route']


    # 2. Enrich your stations with route-relative positions

    stations = [{
        'id': 'STATION_001', 'name': 'Pilot Lost Hills',
        'address': '14808 Warren St, Lost Hills, CA',
        'location': {'lat': 35.6151, 'lng': -119.6588},
        'price_per_gallon': 4.39, 'fuel_type': 'diesel',
    }]

    enriched = requests.post(f'{BASE}/v1/stations/enrich-distances',
        headers=HEADERS, json={'route': route, 'stations': stations}
    ).json()['stations']


    # 3. Optimize

    result = requests.post(f'{BASE}/v1/optimize', headers=HEADERS, json={
        'route': route,
        'stations': enriched,
        'vehicle': {
            'mpg': 6.5,
            'tank_capacity_gallons': 240.0,
            'current_fuel_gallons': 30.0,
        },
    }).json()


    print(f"Recommended stops: {len(result['stops'])}")

    print(f"Net cost:
    ${result['summary']['baseline_comparison']['optimizer_net_fuel_cost']:.2f}")

    ```


    ## Latency


    Approximate response times under typical load (single-region, low concurrent
    traffic):


    - **`/v1/routes`** — ~500-900ms (one Google Maps Directions API call
    dominates).

    - **`/v1/stations/enrich-distances`** — ~300ms-1s for 1-50 stations; scales
    roughly linearly with station count due to Mapbox matrix sizing.

    - **`/v1/optimize`** — ~500ms-2s. Optimizer runtime depends on station count
    and trip distance; trips with more stations and longer distance take longer.


    These are estimates, not SLAs. Set client-side timeouts at 10-15 seconds to
    handle tail latency from upstream providers.


    ## Errors


    All errors return a canonical shape with `error_code`, `message`, optional
    `details`, and `request_id` (always set, useful in support requests). Status
    codes follow standard HTTP semantics: 400 for validation errors, 403 for
    auth, 422 for infeasibility, 429 for rate limits, 500 for unexpected
    failures.


    ## Rate Limits


    Per API key: 60/minute on `/v1/routes`, 120/minute on the other two
    endpoints. Exceeding the limit returns 429 with a `Retry-After` header.


    ## Versioning


    The `/v1/` URL prefix is stable. Within v1, additive changes (new endpoints,
    new optional request fields, new response fields) ship without notice.
    Breaking changes (renamed or removed fields, changed types, changed
    semantics) ship as a new prefix (`/v2/`) with v1 supported in parallel for a
    documented deprecation window. Error codes are stable strings — we add to
    the list but never rename existing ones.
  contact:
    name: Nozle Support
    email: rohan.iyer@nozlerouting.com
  version: 1.0.0
servers: []
security: []
tags:
  - name: v1
    description: >-
      Stable v1 endpoints for fleet partners. These are the only endpoints
      partners should integrate with.
paths:
  /v1/stations/enrich-distances:
    post:
      tags:
        - v1
      summary: Compute route-relative distance and detour time for stations
      description: >-
        Required preparation step before /v1/optimize. Takes a route and a list
        of stations, returns the same stations augmented with
        `miles_from_route_start` (how far along the route the station sits) and
        `detour_minutes` (how much extra driving time the detour adds).


        Two filter stages eliminate stations that wouldn't be useful:


        1. **Coordinate pre-filter** drops stations more than
        `coarse_filter_radius_miles` from the route. Cheap; no API calls.
        Default 7 miles, range 5-20.

        2. **Detour threshold** drops stations whose round-trip detour exceeds
        `max_detour_minutes`. Default 10 minutes, range 1-20.


        Partner-provided fields (`id`, `price_per_gallon`, `brand`, `metadata`,
        etc.) are preserved unchanged — only the two computed fields are added.


        Returns warnings reporting how many stations were dropped at each stage.
      operationId: post_enrich_distances_v1_stations_enrich_distances_post
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/EnrichDistancesRequest'
        required: true
      responses:
        '200':
          description: Stations enriched. Some may be filtered out; see the warnings field.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/EnrichDistancesResponse'
              example:
                stations:
                  - id: partner_xyz_001
                    name: Pilot Travel Center
                    address: 456 Truck Rd, Flagstaff, AZ
                    location:
                      lat: 35.1983
                      lng: -111.6513
                    brand: Pilot
                    price_per_gallon: 4.29
                    fuel_type: diesel
                    miles_from_route_start: 487.3
                    detour_minutes: 3.2
                warnings:
                  - '5 stations dropped: more than 7.0 miles from the route.'
                request_id: req_def789ghi012
        '400':
          description: Validation error (empty stations list, radius out of bounds, etc.).
        '403':
          description: Missing or invalid API key.
        '422':
          description: Validation Error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/HTTPValidationError'
        '429':
          description: Rate limit exceeded (120 requests/minute per key).
        '500':
          description: >-
            Distance matrix service failure or unexpected error. Include the
            request_id when contacting support.
      security:
        - APIKeyHeader: []
components:
  schemas:
    EnrichDistancesRequest:
      properties:
        route:
          $ref: '#/components/schemas/Route'
        stations:
          items:
            $ref: '#/components/schemas/Station'
          type: array
          maxItems: 5000
          minItems: 1
          title: Stations
        coarse_filter_radius_miles:
          type: number
          maximum: 20
          minimum: 5
          title: Coarse Filter Radius Miles
          description: >-
            Stations more than this many miles from the route polyline are
            dropped before the (expensive) distance matrix call. Bounds protect
            against runaway matrix costs. Increase for more lenient inclusion if
            your station coordinates are loose; decrease to cut matrix-call
            costs.
          default: 7
        max_detour_minutes:
          type: number
          maximum: 20
          minimum: 1
          title: Max Detour Minutes
          description: >-
            Stations whose round-trip detour exceeds this many minutes are
            dropped after the matrix call.
          default: 10
      type: object
      required:
        - route
        - stations
      title: EnrichDistancesRequest
    EnrichDistancesResponse:
      properties:
        stations:
          items:
            $ref: '#/components/schemas/Station'
          type: array
          title: Stations
        warnings:
          items:
            type: string
          type: array
          title: Warnings
        request_id:
          type: string
          title: Request Id
      type: object
      required:
        - stations
        - request_id
      title: EnrichDistancesResponse
    HTTPValidationError:
      properties:
        detail:
          items:
            $ref: '#/components/schemas/ValidationError'
          type: array
          title: Detail
      type: object
      title: HTTPValidationError
    Route:
      properties:
        total_distance_miles:
          type: number
          exclusiveMinimum: 0
          title: Total Distance Miles
          description: Total driving distance from start to end, in miles.
        total_duration_minutes:
          type: number
          exclusiveMinimum: 0
          title: Total Duration Minutes
          description: Total driving time from start to end, in minutes (no stops).
        start:
          $ref: '#/components/schemas/Coordinates'
          description: Starting coordinates.
        end:
          $ref: '#/components/schemas/Coordinates'
          description: Ending coordinates.
        polyline:
          anyOf:
            - type: string
            - type: 'null'
          title: Polyline
          description: >-
            Encoded polyline tracing the route. Same format as Google Maps
            `overview_polyline.points`. Either `polyline` or `waypoints` is
            required.
        waypoints:
          anyOf:
            - items:
                $ref: '#/components/schemas/Coordinates'
              type: array
            - type: 'null'
          title: Waypoints
          description: >-
            Explicit list of lat/lng points along the route. Alternative to
            `polyline` if you don't have an encoded form. Either `polyline` or
            `waypoints` is required.
      type: object
      required:
        - total_distance_miles
        - total_duration_minutes
        - start
        - end
      title: Route
      description: |-
        Internal route representation. Flat shape — upstream adapters normalize
        Google/Mapbox responses into this before it hits B2B code.
    Station:
      properties:
        id:
          type: string
          maxLength: 200
          minLength: 1
          title: Id
          description: >-
            Partner-controlled identifier. Treated as opaque — round-trips
            unchanged through every endpoint. Use whatever lets you match the
            station back to your records (database key, address string,
            coordinate hash, etc.).
        name:
          type: string
          maxLength: 200
          minLength: 1
          title: Name
          description: Station name.
        address:
          type: string
          maxLength: 500
          minLength: 1
          title: Address
          description: Street address.
        location:
          $ref: '#/components/schemas/Coordinates'
          description: Station coordinates.
        brand:
          anyOf:
            - type: string
              maxLength: 100
            - type: 'null'
          title: Brand
          description: Optional brand (e.g. 'Shell', 'Pilot').
        price_per_gallon:
          anyOf:
            - type: number
              maximum: 100
              exclusiveMinimum: 0
            - type: 'null'
          title: Price Per Gallon
          description: >-
            Current price per gallon. Required for /v1/optimize. Pre-filter to
            your vehicle's fuel type before sending.
        fuel_type:
          anyOf:
            - type: string
              enum:
                - regular
                - midgrade
                - premium
                - diesel
            - type: 'null'
          title: Fuel Type
          description: >-
            Informational tag. The optimizer treats price_per_gallon as
            authoritative regardless of fuel_type. All stations in one
            /v1/optimize request must share this value (if set) — mixed types
            are rejected to catch integration mistakes.
        miles_from_route_start:
          anyOf:
            - type: number
              minimum: 0
            - type: 'null'
          title: Miles From Route Start
          description: >-
            How far along the route the station sits. Populated by
            /v1/stations/enrich-distances. Required for /v1/optimize.
        detour_minutes:
          anyOf:
            - type: number
              minimum: 0
            - type: 'null'
          title: Detour Minutes
          description: >-
            Round-trip detour time vs. driving past the station. Populated by
            /v1/stations/enrich-distances.
        metadata:
          anyOf:
            - additionalProperties: true
              type: object
            - type: 'null'
          title: Metadata
          description: >-
            Optional partner-defined data passed through every endpoint
            unchanged. Use it for anything you want to associate with a station
            (loyalty tier, internal IDs, etc.).
      type: object
      required:
        - id
        - name
        - address
        - location
      title: Station
      description: |-
        Represents a gas station as it flows through the v1 pipeline. Fields
        accumulate as the station passes through endpoints:

          enrich-distances:  adds miles_from_route_start, detour_minutes
          optimize:          consumes all fields

        Partner-provided fields (id, name, address, location, brand, prices,
        metadata) are always preserved round-trip. Enrichment only adds computed
        fields; it never modifies what the partner sent.
    ValidationError:
      properties:
        loc:
          items:
            anyOf:
              - type: string
              - type: integer
          type: array
          title: Location
        msg:
          type: string
          title: Message
        type:
          type: string
          title: Error Type
        input:
          title: Input
        ctx:
          type: object
          title: Context
      type: object
      required:
        - loc
        - msg
        - type
      title: ValidationError
    Coordinates:
      properties:
        lat:
          type: number
          maximum: 90
          minimum: -90
          title: Lat
          description: Latitude in degrees, -90 to 90.
        lng:
          type: number
          maximum: 180
          minimum: -180
          title: Lng
          description: Longitude in degrees, -180 to 180.
      type: object
      required:
        - lat
        - lng
      title: Coordinates
      description: WGS84 lat/lng pair.
  securitySchemes:
    APIKeyHeader:
      type: apiKey
      in: header
      name: X-API-Key

````