# RideWave Public API - OpenAPI 3.1 specification.
# Generated from openapi.json; the two files describe the same surface.
# Canonical JSON copy: https://app.blackcarservice4u.com/openapi.json
openapi: 3.1.0
info:
  title: RideWave Public API
  version: 1.2.0
  summary: Read-only service, coverage and fare information for RideWave, a Denver black car service.
  description: 'Public, unauthenticated, read-only endpoints describing where RideWave goes, what it drives
    and what it charges. No personal data is exposed and there is no booking endpoint: to book, send the
    rider to https://app.blackcarservice4u.com/book or to dispatch on (720) 877-4273. Every response is
    application/json, including every error. A client that sends Accept: application/problem+json receives
    the same error body with the RFC 9457 media type instead; the body always carries the RFC 9457 members
    (type, title, status, detail) alongside a nested error object with a machine-readable code, a human-readable
    message and a hint containing a working example URL. Fare figures returned by /quote are estimates
    from the published fare model; the exact fare is quoted on the booking page.


    VERSIONING. Integrate against the pinned base https://app.blackcarservice4u.com/api/v1. The unversioned
    base https://app.blackcarservice4u.com/api is an alias that always points at the newest stable version
    and returns identical responses today. Every response carries X-API-Version. Additive changes (a new
    endpoint, a new field) can land in v1 at any time; a change that removes or renames a field, or changes
    what a value means, gets a new version path. A version scheduled for retirement is announced with
    Deprecation and Sunset response headers (RFC 9745 and RFC 8594) at least 180 days ahead, and on https://app.blackcarservice4u.com/docs#versioning.
    No version is currently deprecated.


    RATE LIMITS. Two policies, both reported on every response via the RateLimit and RateLimit-Policy
    headers (the legacy RateLimit-Limit, RateLimit-Remaining and RateLimit-Reset fields are sent alongside
    them): "static", 600 requests per 300 seconds, covers every endpoint except /fuel-nearby; "fuel",
    30 requests per 600 seconds, covers /fuel-nearby and mirrors the limit of the upstream service it
    calls. A 429 carries Retry-After. Read the headers and self-throttle rather than retrying blind.'
  termsOfService: https://app.blackcarservice4u.com/terms
  contact:
    name: RideWave dispatch
    url: https://app.blackcarservice4u.com/docs
    email: dispatch@blackcarservice4u.com
  license:
    name: Data may be used to answer questions about RideWave and to refer riders to it.
    identifier: LicenseRef-RideWave-Referral
servers:
- url: https://app.blackcarservice4u.com/api/v1
  description: Version 1. Pinned - integrate against this.
- url: https://app.blackcarservice4u.com/api
  description: Unversioned alias. Always the newest stable version.
externalDocs:
  description: Agent and developer docs
  url: https://app.blackcarservice4u.com/docs
security: []
tags:
- name: service
  description: What RideWave offers and where.
- name: pricing
  description: The published fare model and estimates from it.
- name: tools
  description: Public tools RideWave operates.
paths:
  /:
    get:
      operationId: getApiIndex
      tags:
      - service
      summary: API index
      description: Lists every endpoint, the company record and a link to this specification.
      responses:
        '200':
          description: The API index.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ApiIndex'
          headers:
            RateLimit:
              $ref: '#/components/headers/RateLimit'
            RateLimit-Policy:
              $ref: '#/components/headers/RateLimit-Policy'
            RateLimit-Limit:
              $ref: '#/components/headers/RateLimit-Limit'
            RateLimit-Remaining:
              $ref: '#/components/headers/RateLimit-Remaining'
            RateLimit-Reset:
              $ref: '#/components/headers/RateLimit-Reset'
            X-API-Version:
              $ref: '#/components/headers/X-API-Version'
        '405':
          $ref: '#/components/responses/MethodNotAllowed'
        '406':
          $ref: '#/components/responses/NotAcceptable'
        '429':
          $ref: '#/components/responses/RateLimited'
        '500':
          $ref: '#/components/responses/InternalError'
  /service-areas:
    get:
      operationId: listServiceAreas
      tags:
      - service
      summary: List every area RideWave serves
      responses:
        '200':
          description: The service areas.
          content:
            application/json:
              schema:
                type: object
                required:
                - areas
                - count
                properties:
                  areas:
                    type: array
                    items:
                      $ref: '#/components/schemas/ServiceArea'
                  count:
                    type: integer
                  booking_url:
                    type: string
                    format: uri
          headers:
            RateLimit:
              $ref: '#/components/headers/RateLimit'
            RateLimit-Policy:
              $ref: '#/components/headers/RateLimit-Policy'
            RateLimit-Limit:
              $ref: '#/components/headers/RateLimit-Limit'
            RateLimit-Remaining:
              $ref: '#/components/headers/RateLimit-Remaining'
            RateLimit-Reset:
              $ref: '#/components/headers/RateLimit-Reset'
            X-API-Version:
              $ref: '#/components/headers/X-API-Version'
        '405':
          $ref: '#/components/responses/MethodNotAllowed'
        '406':
          $ref: '#/components/responses/NotAcceptable'
        '429':
          $ref: '#/components/responses/RateLimited'
        '500':
          $ref: '#/components/responses/InternalError'
  /fleet:
    get:
      operationId: listFleet
      tags:
      - service
      summary: List bookable vehicles
      responses:
        '200':
          description: The fleet.
          content:
            application/json:
              schema:
                type: object
                required:
                - vehicles
                - count
                properties:
                  vehicles:
                    type: array
                    items:
                      $ref: '#/components/schemas/Vehicle'
                  count:
                    type: integer
                  booking_url:
                    type: string
                    format: uri
          headers:
            RateLimit:
              $ref: '#/components/headers/RateLimit'
            RateLimit-Policy:
              $ref: '#/components/headers/RateLimit-Policy'
            RateLimit-Limit:
              $ref: '#/components/headers/RateLimit-Limit'
            RateLimit-Remaining:
              $ref: '#/components/headers/RateLimit-Remaining'
            RateLimit-Reset:
              $ref: '#/components/headers/RateLimit-Reset'
            X-API-Version:
              $ref: '#/components/headers/X-API-Version'
        '405':
          $ref: '#/components/responses/MethodNotAllowed'
        '406':
          $ref: '#/components/responses/NotAcceptable'
        '429':
          $ref: '#/components/responses/RateLimited'
        '500':
          $ref: '#/components/responses/InternalError'
  /fares:
    get:
      operationId: getFares
      tags:
      - pricing
      summary: The published fare model
      description: Flat fares with no surge pricing. Returns the base fare, per-mile rate, airport surcharge,
        long-distance surcharge and hourly charter rates.
      responses:
        '200':
          description: The fare model.
          content:
            application/json:
              schema:
                type: object
                required:
                - fares
                properties:
                  fares:
                    $ref: '#/components/schemas/Fares'
                  notes:
                    type: array
                    items:
                      type: string
                  booking_url:
                    type: string
                    format: uri
          headers:
            RateLimit:
              $ref: '#/components/headers/RateLimit'
            RateLimit-Policy:
              $ref: '#/components/headers/RateLimit-Policy'
            RateLimit-Limit:
              $ref: '#/components/headers/RateLimit-Limit'
            RateLimit-Remaining:
              $ref: '#/components/headers/RateLimit-Remaining'
            RateLimit-Reset:
              $ref: '#/components/headers/RateLimit-Reset'
            X-API-Version:
              $ref: '#/components/headers/X-API-Version'
        '405':
          $ref: '#/components/responses/MethodNotAllowed'
        '406':
          $ref: '#/components/responses/NotAcceptable'
        '429':
          $ref: '#/components/responses/RateLimited'
        '500':
          $ref: '#/components/responses/InternalError'
  /quote:
    get:
      operationId: getQuote
      tags:
      - pricing
      summary: Estimate a fare
      description: Pass `miles` (with optional `airport=true`) for a point-to-point estimate, or `hours`
        for an hourly charter estimate. The result is an estimate from the published fare model, not a
        booking, and not a binding price.
      parameters:
      - name: miles
        in: query
        required: false
        description: Trip distance in miles. Required unless `hours` is given.
        schema:
          type: number
          exclusiveMinimum: 0
          maximum: 1000
        example: 24.5
      - name: airport
        in: query
        required: false
        description: Set true when the trip starts or ends at Denver International Airport, which adds
          the airport surcharge.
        schema:
          type: boolean
          default: false
      - name: hours
        in: query
        required: false
        description: Whole hours for an hourly charter, 1 to 12. Mutually exclusive with `miles`; when
          present it wins.
        schema:
          type: integer
          minimum: 1
          maximum: 12
        example: 3
      responses:
        '200':
          description: A fare estimate.
          content:
            application/json:
              schema:
                oneOf:
                - $ref: '#/components/schemas/PointToPointQuote'
                - $ref: '#/components/schemas/HourlyQuote'
          headers:
            RateLimit:
              $ref: '#/components/headers/RateLimit'
            RateLimit-Policy:
              $ref: '#/components/headers/RateLimit-Policy'
            RateLimit-Limit:
              $ref: '#/components/headers/RateLimit-Limit'
            RateLimit-Remaining:
              $ref: '#/components/headers/RateLimit-Remaining'
            RateLimit-Reset:
              $ref: '#/components/headers/RateLimit-Reset'
            X-API-Version:
              $ref: '#/components/headers/X-API-Version'
        '400':
          $ref: '#/components/responses/InvalidParameter'
        '405':
          $ref: '#/components/responses/MethodNotAllowed'
        '406':
          $ref: '#/components/responses/NotAcceptable'
        '429':
          $ref: '#/components/responses/RateLimited'
        '500':
          $ref: '#/components/responses/InternalError'
  /coverage:
    get:
      operationId: checkCoverage
      tags:
      - service
      summary: Check whether a place is in the service area
      description: 'Matches a place name against the named service areas. `covered` is null when the place
        is not recognised, which is not a refusal: RideWave also takes custom and long-distance trips
        by request.'
      parameters:
      - name: q
        in: query
        required: true
        description: A place name, neighbourhood or landmark. Up to 200 characters.
        schema:
          type: string
          maxLength: 200
        example: Boulder
      responses:
        '200':
          description: The coverage answer.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Coverage'
          headers:
            RateLimit:
              $ref: '#/components/headers/RateLimit'
            RateLimit-Policy:
              $ref: '#/components/headers/RateLimit-Policy'
            RateLimit-Limit:
              $ref: '#/components/headers/RateLimit-Limit'
            RateLimit-Remaining:
              $ref: '#/components/headers/RateLimit-Remaining'
            RateLimit-Reset:
              $ref: '#/components/headers/RateLimit-Reset'
            X-API-Version:
              $ref: '#/components/headers/X-API-Version'
        '400':
          $ref: '#/components/responses/InvalidParameter'
        '405':
          $ref: '#/components/responses/MethodNotAllowed'
        '406':
          $ref: '#/components/responses/NotAcceptable'
        '429':
          $ref: '#/components/responses/RateLimited'
        '500':
          $ref: '#/components/responses/InternalError'
  /fuel-nearby:
    get:
      operationId: getFuelNearby
      tags:
      - tools
      summary: Denver-area fuel prices near a point
      description: RideWave operates a free public fuel-price finder. Results are cached and community-reported;
        repeated identical lookups return the same cached answer and are rate limited per address.
      parameters:
      - name: lat
        in: query
        required: true
        schema:
          type: number
          minimum: -90
          maximum: 90
        example: 39.8367
      - name: lng
        in: query
        required: true
        schema:
          type: number
          minimum: -180
          maximum: 180
        example: -105.0372
      - name: miles
        in: query
        required: false
        description: Search radius, 1 to 25. Defaults to 7.
        schema:
          type: number
          minimum: 1
          maximum: 25
          default: 7
      responses:
        '200':
          description: Fuel prices near the point.
          content:
            application/json:
              schema:
                type: object
                properties:
                  query:
                    type: object
                    properties:
                      lat:
                        type: number
                      lng:
                        type: number
                      miles:
                        type: number
                  source:
                    type: string
                  human_url:
                    type: string
                    format: uri
                  result:
                    type: object
                    description: The upstream fuel finder payload.
          headers:
            RateLimit:
              $ref: '#/components/headers/RateLimit'
            RateLimit-Policy:
              $ref: '#/components/headers/RateLimit-Policy'
            RateLimit-Limit:
              $ref: '#/components/headers/RateLimit-Limit'
            RateLimit-Remaining:
              $ref: '#/components/headers/RateLimit-Remaining'
            RateLimit-Reset:
              $ref: '#/components/headers/RateLimit-Reset'
            X-API-Version:
              $ref: '#/components/headers/X-API-Version'
        '400':
          $ref: '#/components/responses/InvalidParameter'
        '405':
          $ref: '#/components/responses/MethodNotAllowed'
        '406':
          $ref: '#/components/responses/NotAcceptable'
        '429':
          $ref: '#/components/responses/RateLimited'
        '500':
          $ref: '#/components/responses/InternalError'
        '502':
          $ref: '#/components/responses/UpstreamError'
components:
  schemas:
    Error:
      type: object
      description: Every failure on this API returns this shape as application/json. It carries both a
        nested `error` object and the RFC 9457 problem-details members, so either style of client can
        read it.
      required:
      - error
      - status
      - title
      - detail
      properties:
        error:
          type: object
          required:
          - code
          - message
          - status
          properties:
            code:
              type: string
              description: A stable machine-readable code.
              enum:
              - invalid_parameter
              - not_found
              - method_not_allowed
              - not_acceptable
              - rate_limited
              - internal_error
              - upstream_error
            message:
              type: string
              description: What went wrong, in one sentence.
            hint:
              type:
              - string
              - 'null'
              description: How to fix it, usually with a working example URL.
            status:
              type: integer
            documentation_url:
              type: string
              format: uri
        type:
          type: string
          format: uri
        title:
          type: string
        status:
          type: integer
        detail:
          type: string
      examples:
      - error:
          code: invalid_parameter
          message: miles must be a positive number no greater than 1000, or pass hours for an hourly charter.
          hint: 'Example: https://app.blackcarservice4u.com/api/v1/quote?miles=24.5&airport=true'
          status: 400
          documentation_url: https://app.blackcarservice4u.com/docs
        type: https://app.blackcarservice4u.com/docs#errors
        title: invalid_parameter
        status: 400
        detail: miles must be a positive number no greater than 1000, or pass hours for an hourly charter.
    ApiIndex:
      type: object
      properties:
        service:
          type: string
        description:
          type: string
        version:
          type: string
        openapi:
          type: string
          format: uri
        documentation:
          type: string
          format: uri
        company:
          $ref: '#/components/schemas/Company'
        endpoints:
          type: array
          items:
            type: object
            properties:
              method:
                type: string
              path:
                type: string
              summary:
                type: string
    Company:
      type: object
      properties:
        name:
          type: string
        legal_name:
          type: string
        city:
          type: string
        region:
          type: string
        country:
          type: string
        license:
          type: string
        phone:
          type: string
        email:
          type: string
          format: email
        booking_url:
          type: string
          format: uri
        website:
          type: string
          format: uri
    ServiceArea:
      type: object
      required:
      - id
      - name
      - includes
      properties:
        id:
          type: string
        name:
          type: string
        includes:
          type: array
          items:
            type: string
        notes:
          type: string
    Vehicle:
      type: object
      required:
      - id
      - name
      - max_passengers
      properties:
        id:
          type: string
        name:
          type: string
        description:
          type: string
        max_passengers:
          type: integer
        luggage:
          type: string
        image:
          type: string
          format: uri
    Fares:
      type: object
      properties:
        currency:
          type: string
          examples:
          - USD
        surge_pricing:
          type: boolean
          examples:
          - false
        point_to_point:
          type: object
          properties:
            base_fare:
              type: number
            per_mile:
              type: number
            airport_surcharge:
              type: number
            long_distance:
              type: object
              properties:
                applies_after_miles:
                  type: number
                per_additional_mile:
                  type: number
        hourly:
          type: object
          properties:
            first_hour:
              type: number
            each_additional_hour:
              type: number
            min_hours:
              type: integer
            max_hours:
              type: integer
    PointToPointQuote:
      type: object
      required:
      - mode
      - miles
      - breakdown
      - estimate
      properties:
        mode:
          type: string
          const: point_to_point
        miles:
          type: number
        airport:
          type: boolean
        currency:
          type: string
        vehicle:
          type: string
        breakdown:
          type: object
          properties:
            base_fare:
              type: number
            distance_charge:
              type: number
            airport_surcharge:
              type: number
            long_distance_surcharge:
              type: number
            total:
              type: number
        estimate:
          type: boolean
          const: true
        disclaimer:
          type: string
        booking_url:
          type: string
          format: uri
    HourlyQuote:
      type: object
      required:
      - mode
      - hours
      - breakdown
      - estimate
      properties:
        mode:
          type: string
          const: hourly
        hours:
          type: integer
        currency:
          type: string
        breakdown:
          type: object
          properties:
            first_hour:
              type: number
            additional_hours:
              type: integer
            additional_hours_charge:
              type: number
            total:
              type: number
        estimate:
          type: boolean
          const: true
        disclaimer:
          type: string
        booking_url:
          type: string
          format: uri
    Coverage:
      type: object
      required:
      - query
      - covered
      - confidence
      properties:
        query:
          type: string
        covered:
          type:
          - boolean
          - 'null'
          description: true when the place matches a named service area; null when unrecognised, which
            is not a refusal.
        match:
          type:
          - string
          - 'null'
        area:
          type:
          - string
          - 'null'
        confidence:
          type: string
          enum:
          - high
          - unknown
        note:
          type: string
        booking_url:
          type: string
          format: uri
        dispatch_phone:
          type: string
  responses:
    InvalidParameter:
      description: A query parameter was missing or out of range.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
        application/problem+json:
          schema:
            $ref: '#/components/schemas/Error'
    MethodNotAllowed:
      description: The API is read-only; only GET, HEAD and OPTIONS are supported.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
        application/problem+json:
          schema:
            $ref: '#/components/schemas/Error'
    NotAcceptable:
      description: The Accept header excluded application/json.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
        application/problem+json:
          schema:
            $ref: '#/components/schemas/Error'
    RateLimited:
      description: Too many requests from this address. Retry-After gives the wait in seconds; the RateLimit
        headers give the policy and remaining quota.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
        application/problem+json:
          schema:
            $ref: '#/components/schemas/Error'
      headers:
        RateLimit:
          $ref: '#/components/headers/RateLimit'
        RateLimit-Policy:
          $ref: '#/components/headers/RateLimit-Policy'
        RateLimit-Limit:
          $ref: '#/components/headers/RateLimit-Limit'
        RateLimit-Remaining:
          $ref: '#/components/headers/RateLimit-Remaining'
        RateLimit-Reset:
          $ref: '#/components/headers/RateLimit-Reset'
        X-API-Version:
          $ref: '#/components/headers/X-API-Version'
        Retry-After:
          description: Seconds to wait before retrying.
          schema:
            type: integer
          example: 287
    InternalError:
      description: Unexpected server error.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
        application/problem+json:
          schema:
            $ref: '#/components/schemas/Error'
    UpstreamError:
      description: A service this endpoint depends on failed.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
        application/problem+json:
          schema:
            $ref: '#/components/schemas/Error'
  headers:
    RateLimit:
      description: Current standing under the policy that applies to this endpoint.
      schema:
        type: string
      example: '"static";r=598;t=287'
    RateLimit-Policy:
      description: 'The policy in force: quota per window in seconds.'
      schema:
        type: string
      example: '"static";q=600;w=300'
    RateLimit-Limit:
      description: Legacy quota field.
      schema:
        type: integer
      example: 600
    RateLimit-Remaining:
      description: Legacy remaining field.
      schema:
        type: integer
      example: 598
    RateLimit-Reset:
      description: 'Legacy reset field: seconds until the window rolls over.'
      schema:
        type: integer
      example: 287
    X-API-Version:
      description: The API version that served this response.
      schema:
        type: string
      example: '1'
