openapi: 3.0.3

info:
  title: TrackMind API
  version: 1.0.0
  description: |
    REST API for integrating with the TrackMind GPS platform.

    ## Authentication
    All requests require an API key in the header:
    ```
    Authorization: Bearer tm_your_key_here
    ```
    Keys are created in the dashboard: **Settings -> API -> Create Key**.

    ## Scopes
    | Scope | Permissions |
    |---|---|
    | `read` | Read vehicles, positions, tracks, geofences, events |
    | `full` | Read + create/update/delete webhooks and geofences |

    ## Data formats
    - Requests and responses: **JSON**
    - Timestamps: **ISO 8601 UTC** (`2026-06-01T08:30:00.000Z`)
    - Identifiers: **UUID v4**
    - Maximum track period: **31 days**

  contact:
    email: support@trackmind.ru
  license:
    name: Proprietary

servers:
  - url: https://api.trackmind.ru
    description: Production

security:
  - BearerAuth: []

tags:
  - name: Vehicles
    description: Vehicles and their positions
  - name: Track
    description: Route history
  - name: Geofences
    description: Geofences and entry/exit events
  - name: Webhooks
    description: Webhooks for real-time event delivery
  - name: API Keys
    description: API key management

components:
  securitySchemes:
    BearerAuth:
      type: http
      scheme: bearer
      description: "API key in format `tm_` + 48 hex chars. Create in Settings -> API."

  schemas:

    Error:
      type: object
      properties:
        error:
          type: string
          example: Error description
      required: [error]

    Position:
      type: object
      properties:
        lat:
          type: number
          format: double
          example: 55.7558
        lon:
          type: number
          format: double
          example: 37.6173
        speed:
          type: integer
          description: Speed in km/h
          example: 42
        heading:
          type: integer
          description: Heading in degrees (0-360)
          example: 270
        satellites:
          type: integer
          example: 12
        time:
          type: string
          format: date-time
          example: "2026-06-01T08:30:00.000Z"
        ignition:
          type: boolean
          nullable: true
          description: "Engine ignition state. null if the tracker does not report this field."

    Vehicle:
      type: object
      properties:
        id:
          type: string
          format: uuid
        name:
          type: string
          example: Truck-001
        license_plate:
          type: string
          example: A123BC77
          nullable: true
        make:
          type: string
          example: Ford
          nullable: true
        model:
          type: string
          example: Transit
          nullable: true
        driver_name:
          type: string
          example: John Smith
          nullable: true
        driver_phone:
          type: string
          example: "+7 900 123-45-67"
          nullable: true
        speed_limit:
          type: integer
          description: Speed alert threshold (km/h)
          example: 90
        fuel_consumption_base:
          type: number
          format: double
          description: Baseline fuel consumption (L/100km from vehicle spec)
          example: 12.5
          nullable: true
        marker_type:
          type: string
          example: van-large
          nullable: true
        group_id:
          type: string
          format: uuid
          nullable: true
        group_name:
          type: string
          nullable: true
        group_color:
          type: string
          example: "#4F8EF7"
          nullable: true
        device_imei:
          type: string
          example: "123456789012345"
          nullable: true
        comment:
          type: string
          nullable: true
          description: "Internal note about the vehicle"
        group_marker_type:
          type: string
          nullable: true
          description: "Marker type inherited from the group"
        device_id:
          type: string
          format: uuid
          nullable: true
          description: "Internal tracker device UUID"
        device_protocol:
          type: string
          nullable: true
          example: gt06
          description: "Tracker communication protocol: gt06, teltonika, wialon"
        device_model:
          type: string
          nullable: true
          description: "Tracker model name"
        position:
          allOf:
            - $ref: '#/components/schemas/Position'
          nullable: true
          description: "Last known position. null if tracker has not sent any data yet."

    TrackPoint:
      type: object
      properties:
        lat:
          type: number
          format: double
          example: 55.7558
        lon:
          type: number
          format: double
          example: 37.6173
        speed:
          type: integer
          example: 54
        heading:
          type: integer
          example: 90
        time:
          type: string
          format: date-time
          example: "2026-06-01T06:02:00.000Z"
        gps_status:
          type: string
          enum: [valid, invalid]
          description: "`valid` = reliable point, `invalid` = anomaly (filtered)"

    Stop:
      type: object
      properties:
        lat:
          type: number
          format: double
        lon:
          type: number
          format: double
        start:
          type: string
          format: date-time
        end:
          type: string
          format: date-time
        duration_minutes:
          type: integer
          example: 15

    TrackStats:
      type: object
      properties:
        distance_km:
          type: number
          format: double
          example: 142.3
        moving_minutes:
          type: integer
          example: 198
        stop_minutes:
          type: integer
          example: 42
        max_speed:
          type: integer
          example: 112
        avg_speed:
          type: integer
          example: 73
        interpolated_distance_km:
          type: number
          format: double
          example: 0

    Geofence:
      type: object
      properties:
        id:
          type: string
          format: uuid
        name:
          type: string
          example: Moscow Warehouse
        type:
          type: string
          enum: [circle, polygon, preset]
        color:
          type: string
          example: "#22C55E"
        center_lat:
          type: number
          format: double
          nullable: true
          example: 55.7558
        center_lon:
          type: number
          format: double
          nullable: true
          example: 37.6173
        radius_m:
          type: integer
          nullable: true
          example: 500
        points:
          type: array
          items:
            type: array
            items:
              type: number
          nullable: true
          description: "Polygon coordinates array [[lat, lon], ...]"
        is_active:
          type: boolean
        notify_enter:
          type: boolean
        notify_exit:
          type: boolean
        vehicle_ids:
          type: array
          items:
            type: string
            format: uuid
        group_ids:
          type: array
          items:
            type: string
            format: uuid
        created_at:
          type: string
          format: date-time

    GeofenceEvent:
      type: object
      properties:
        id:
          type: string
          format: uuid
        event_type:
          type: string
          enum: [enter, exit]
        lat:
          type: number
          format: double
        lon:
          type: number
          format: double
        occurred_at:
          type: string
          format: date-time
        geofence_id:
          type: string
          format: uuid
        geofence_name:
          type: string
        geofence_color:
          type: string
        vehicle_id:
          type: string
          format: uuid
        vehicle_name:
          type: string
        license_plate:
          type: string
          nullable: true

    Webhook:
      type: object
      properties:
        id:
          type: string
          format: uuid
        url:
          type: string
          format: uri
          example: https://your-server.com/webhook
        has_secret:
          type: boolean
          description: Whether a signing secret is configured
        events:
          type: array
          items:
            type: string
            enum: [position, geofence.enter, geofence.exit, speed.exceeded, device.offline, device.online]
          example: [position, geofence.enter]
        enabled:
          type: boolean
        last_triggered_at:
          type: string
          format: date-time
          nullable: true
        last_status:
          type: integer
          nullable: true
          example: 200
        created_at:
          type: string
          format: date-time

    ApiKey:
      type: object
      properties:
        id:
          type: string
          format: uuid
        name:
          type: string
          example: CRM Integration
        key_prefix:
          type: string
          example: tm_a1b2c3d4e5f6
          description: First 12 characters of the key for identification
        scope:
          type: string
          enum: [read, full]
        last_used_at:
          type: string
          format: date-time
          nullable: true
        created_at:
          type: string
          format: date-time

    ApiKeyCreated:
      allOf:
        - $ref: '#/components/schemas/ApiKey'
        - type: object
          properties:
            key:
              type: string
              description: "Full API key. Shown ONLY ONCE at creation - save it immediately."
              example: tm_a1b2c3d4e5f6...full_48_char_key

paths:

  /vehicles:
    get:
      tags: [Vehicles]
      summary: List vehicles
      description: |
        Returns all vehicles in the account with their last known positions.
        The `position` field is `null` if the tracker has not sent any data yet.
      responses:
        '200':
          description: Success
          content:
            application/json:
              schema:
                type: object
                properties:
                  vehicles:
                    type: array
                    items:
                      $ref: '#/components/schemas/Vehicle'
        '401':
          description: Invalid API key
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '403':
          description: Insufficient permissions
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'

  /vehicles/{id}:
    get:
      tags: [Vehicles]
      summary: Get vehicle
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
            format: uuid
      responses:
        '200':
          description: Success
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Vehicle'
        '404':
          description: Vehicle not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'

  /vehicles/{id}/last:
    get:
      tags: [Vehicles]
      summary: Last position
      description: Returns coordinates only, without vehicle metadata. Useful for polling.
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
            format: uuid
      responses:
        '200':
          description: Success
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/Position'
                nullable: true

  /vehicles/{id}/track:
    get:
      tags: [Track]
      summary: Route history
      description: |
        Full movement history: coordinates, stops, and statistics.
        Maximum period: **31 days**. Returns 400 if exceeded.
        Points are anti-spoof filtered and downsampled to 5,000 per request.
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
            format: uuid
        - name: from
          in: query
          required: true
          description: "Period start (ISO 8601 UTC)"
          schema:
            type: string
            format: date-time
          example: "2026-06-01T00:00:00Z"
        - name: to
          in: query
          required: true
          description: "Period end (ISO 8601 UTC)"
          schema:
            type: string
            format: date-time
          example: "2026-06-01T23:59:59Z"
        - name: min_stop
          in: query
          required: false
          description: "Minimum stop duration in minutes (default: 5)"
          schema:
            type: integer
            default: 5
            minimum: 1
        - name: interpolate
          in: query
          required: false
          description: "Set to 1 to enable route interpolation for signal gaps"
          schema:
            type: integer
            enum: [0, 1]
            default: 0
      responses:
        '200':
          description: Success
          content:
            application/json:
              schema:
                type: object
                properties:
                  points:
                    type: array
                    items:
                      $ref: '#/components/schemas/TrackPoint'
                  stops:
                    type: array
                    items:
                      $ref: '#/components/schemas/Stop'
                  stats:
                    $ref: '#/components/schemas/TrackStats'
                  interpolated_segments:
                    type: array
                    items:
                      type: object
        '400':
          description: Invalid period (exceeds 31 days or invalid dates)
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'

  /geofences:
    get:
      tags: [Geofences]
      summary: List geofences
      responses:
        '200':
          description: Success
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/Geofence'

  /geofences/{id}:
    get:
      tags: [Geofences]
      summary: Get geofence
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
            format: uuid
      responses:
        '200':
          description: Success
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Geofence'
        '404':
          description: Not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'

  /geofences/events:
    get:
      tags: [Geofences]
      summary: Geofence events
      description: Entry/exit event history with filtering. Maximum 1,000 records.
      parameters:
        - name: from
          in: query
          schema:
            type: string
            format: date-time
        - name: to
          in: query
          schema:
            type: string
            format: date-time
        - name: geofence_id
          in: query
          schema:
            type: string
            format: uuid
        - name: vehicle_id
          in: query
          schema:
            type: string
            format: uuid
        - name: limit
          in: query
          schema:
            type: integer
            default: 200
            maximum: 1000
      responses:
        '200':
          description: Success
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/GeofenceEvent'

  /webhooks:
    get:
      tags: [Webhooks]
      summary: List webhooks
      responses:
        '200':
          description: Success
          content:
            application/json:
              schema:
                type: object
                properties:
                  webhooks:
                    type: array
                    items:
                      $ref: '#/components/schemas/Webhook'

    post:
      tags: [Webhooks]
      summary: Create webhook
      description: Requires scope `full`.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [url]
              properties:
                url:
                  type: string
                  format: uri
                  example: https://your-server.com/webhook
                secret:
                  type: string
                  description: "Secret for HMAC signing of X-TrackMind-Signature header"
                  example: my-secret-key
                events:
                  type: array
                  items:
                    type: string
                    enum: [position, geofence.enter, geofence.exit, speed.exceeded, device.offline, device.online]
                  default: [position]
      responses:
        '201':
          description: Created
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Webhook'

  /webhooks/{id}:
    put:
      tags: [Webhooks]
      summary: Update webhook
      description: Send only the fields you want to change. Requires scope `full`.
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
            format: uuid
      requestBody:
        content:
          application/json:
            schema:
              type: object
              properties:
                url:
                  type: string
                  format: uri
                secret:
                  type: string
                  nullable: true
                events:
                  type: array
                  items:
                    type: string
                enabled:
                  type: boolean
      responses:
        '200':
          description: Updated
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Webhook'

    delete:
      tags: [Webhooks]
      summary: Delete webhook
      description: Requires scope `full`.
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
            format: uuid
      responses:
        '204':
          description: Deleted

  /webhooks/{id}/test:
    post:
      tags: [Webhooks]
      summary: Send test request
      description: Sends a test payload to the webhook URL and returns the delivery result.
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
            format: uuid
      responses:
        '200':
          description: Delivery result
          content:
            application/json:
              schema:
                type: object
                properties:
                  ok:
                    type: boolean
                  status:
                    type: integer
                    example: 200
                  error:
                    type: string
                    nullable: true

  /api-keys:
    get:
      tags: [API Keys]
      summary: List API keys
      description: Full key value is never returned - only the prefix for identification.
      responses:
        '200':
          description: Success
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/ApiKey'

    post:
      tags: [API Keys]
      summary: Create API key
      description: |
        The full key is returned **only in this response** - save it immediately.
        Maximum 10 keys per account. Available to owner and admin roles only.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [name]
              properties:
                name:
                  type: string
                  example: CRM Integration
                scope:
                  type: string
                  enum: [read, full]
                  default: read
      responses:
        '201':
          description: Created
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ApiKeyCreated'

  /api-keys/{id}:
    delete:
      tags: [API Keys]
      summary: Revoke API key
      description: Key is immediately deactivated. All requests using it will return 401.
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
            format: uuid
      responses:
        '200':
          description: Revoked
          content:
            application/json:
              schema:
                type: object
                properties:
                  ok:
                    type: boolean
                    example: true
