openapi: 3.1.0
info:
  title: Telemax External API (V2)
  version: '2026-04-14'
  description: 'Telemax External API — fleet telemetry and integrations.


    **Base URL:** `https://api.telemax.com.au`


    **Authentication:** All routes require a `Bearer` JWT unless marked `[AllowAnonymous]`.

    Obtain tokens via `POST /v2/api/authentication/token/api-key`.


    **RESTful architecture:** V2 uses standard HTTP methods (GET, POST, PUT, DELETE) with

    resource-based routes. Paginated endpoints take `page` and `pageSize` query parameters;

    battery health takes `pageNumber` and `pageSize` instead.


    **API versioning:** Responses carry `X-API-Version: 2`, except a `401` for a missing, invalid

    or expired bearer token and a `429`. See the [Changelog](/v2/changelog) for the deprecation

    policy and version history.


    For full documentation including error handling, pagination, and known field quirks, see [docs.telemax.com.au](https://docs.telemax.com.au).

    '
servers:
- url: https://api.telemax.com.au
security:
- BearerAuth: []
components:
  schemas:
    ApiError:
      type: object
      description: Standard error response returned by all API endpoints.
      properties:
        type:
          type: string
          description: Error category (AUTHENTICATION, AUTHORIZATION, RESOURCE, RESOURCE_CONFLICT, VALIDATION_ERROR, RATE_LIMIT, SERVER_ERROR, SERVER).
          example: AUTHENTICATION
        code:
          type: string
          description: Machine-readable error code within the category.
          example: INVALID_CREDENTIALS
        description:
          type: string
          description: Human-readable explanation of the error and how to resolve it.
          example: The provided API key is invalid or does not exist.
        requestId:
          type: string
          description: Unique request identifier for support correlation.
          example: '0HNP2MAV4BUCA:00000001'
        docUrl:
          type: string
          format: uri
          description: Link to the relevant error documentation page.
          example: https://docs.telemax.com.au/errors/invalid-credentials
      required:
      - type
      - code
      - description
    CompanySuspendedError:
      type: object
      description: Body of the 403 returned on every authenticated endpoint while the API key's company is suspended or otherwise not active, or a company above it is. It is not an ApiError and has no requestId or docUrl.
      properties:
        code:
          type: string
          example: CompanySuspended
        message:
          type: string
          example: The company has been suspended. Please contact your administrator.
      required:
      - code
      - message
  responses:
    Unauthorized:
      description: '**401 Unauthorized** — The bearer token is missing, invalid, or expired.

        Rejected before rate limiting, so the response has no `X-RateLimit-*` or `X-API-Version` headers.

        '
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ApiError'
          example:
            type: AUTHENTICATION
            code: INVALID_CREDENTIALS
            description: The provided API key is invalid or does not exist.
            requestId: '0HNP2MAV4BUCA:00000002'
            docUrl: https://docs.telemax.com.au/errors/invalid-credentials
    Forbidden:
      description: '**403 Forbidden** — Either the API key lacks the permission this endpoint requires (`INVALID_SCOPE`),

        or the company the key belongs to (or a company above it) is suspended or not active. That response has its own body,

        `{code, message}`, with no `requestId` or `docUrl`. See [Errors](/v2/errors#403-forbidden).

        '
      headers:
        X-RateLimit-Limit:
          $ref: '#/components/headers/X-RateLimit-Limit'
        X-RateLimit-Remaining:
          $ref: '#/components/headers/X-RateLimit-Remaining'
        X-RateLimit-Reset:
          $ref: '#/components/headers/X-RateLimit-Reset'
        X-RateLimit-Policy:
          $ref: '#/components/headers/X-RateLimit-Policy'
      content:
        application/json:
          schema:
            oneOf:
            - $ref: '#/components/schemas/ApiError'
            - $ref: '#/components/schemas/CompanySuspendedError'
          examples:
            invalidScope:
              summary: The API key lacks this endpoint's permission
              value:
                type: AUTHORIZATION
                code: INVALID_SCOPE
                description: The provided token does not have permission to access this resource.
                requestId: '0HNP2MAV4BUCA:00000003'
                docUrl: https://docs.telemax.com.au/errors/invalid-scope
            companySuspended:
              summary: The company is suspended
              value:
                code: CompanySuspended
                message: The company has been suspended. Please contact your administrator.
    UnprocessableEntity:
      description: '**422 Unprocessable Entity** — A route, query or body value is missing or cannot be parsed (every such

        model-binding failure returns the message shown), or the endpoint rejected a value with its own message.

        '
      headers:
        X-RateLimit-Limit:
          $ref: '#/components/headers/X-RateLimit-Limit'
        X-RateLimit-Remaining:
          $ref: '#/components/headers/X-RateLimit-Remaining'
        X-RateLimit-Reset:
          $ref: '#/components/headers/X-RateLimit-Reset'
        X-RateLimit-Policy:
          $ref: '#/components/headers/X-RateLimit-Policy'
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ApiError'
          example:
            type: VALIDATION_ERROR
            code: INVALID_INPUT
            description: One or more route or query parameters could not be parsed.
            requestId: '0HNP2MAV4BUCA:00000004'
            docUrl: https://docs.telemax.com.au/errors/invalid-input
    TooManyRequests:
      description: '**429 Too Many Requests** — A rate-limit window for this API key and endpoint is exhausted.

        `Retry-After` is the length of that window in seconds (1, 60, 3600 or 86400); waiting that long clears it. If another

        window is also used up, the retry gets a new 429 with that window''s `Retry-After`.

        The response has no `X-API-Version` header. See [Rate limiting](/v2/request-response#rate-limiting).

        '
      headers:
        X-RateLimit-Limit:
          $ref: '#/components/headers/X-RateLimit-Limit'
        X-RateLimit-Remaining:
          $ref: '#/components/headers/X-RateLimit-Remaining'
        X-RateLimit-Reset:
          $ref: '#/components/headers/X-RateLimit-Reset'
        X-RateLimit-Policy:
          $ref: '#/components/headers/X-RateLimit-Policy'
        Retry-After:
          $ref: '#/components/headers/Retry-After'
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ApiError'
          example:
            type: RATE_LIMIT
            code: RATE_LIMIT_EXCEEDED
            description: Too many requests. Please slow down.
            requestId: '0HNP2MAV4BUCB:00000001'
            docUrl: https://docs.telemax.com.au/errors/rate-limit-exceeded
    InternalServerError:
      description: '**500 Internal Server Error** — An unexpected error occurred: `SERVER_ERROR`/`INTERNAL_SERVER_ERROR`, or

        `SERVER`/`INTERNAL_ERROR` for an unhandled exception. Include the requestId when contacting support.

        '
      headers:
        X-RateLimit-Limit:
          $ref: '#/components/headers/X-RateLimit-Limit'
        X-RateLimit-Remaining:
          $ref: '#/components/headers/X-RateLimit-Remaining'
        X-RateLimit-Reset:
          $ref: '#/components/headers/X-RateLimit-Reset'
        X-RateLimit-Policy:
          $ref: '#/components/headers/X-RateLimit-Policy'
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ApiError'
          example:
            type: SERVER_ERROR
            code: INTERNAL_SERVER_ERROR
            description: An unexpected error occurred. Please try again later.
            requestId: '0HNP2MAV4BUCB:00000002'
            docUrl: https://docs.telemax.com.au/errors/internal-server-error
  headers:
    X-RateLimit-Limit:
      description: Request limit of the most constrained window for this API key on this endpoint. Sent on every response of a rate-limited endpoint that counts against the limit.
      schema:
        type: integer
        example: 20
    X-RateLimit-Remaining:
      description: Requests remaining in that window (counted after this request).
      schema:
        type: integer
        example: 17
    X-RateLimit-Reset:
      description: Approximate Unix timestamp (seconds) at which that window resets. Do not use it to time a retry; use `Retry-After`.
      schema:
        type: integer
        example: 1746000060
    X-RateLimit-Policy:
      description: The window the limit and remaining count refer to.
      schema:
        type: string
        enum:
        - second
        - minute
        - hour
        - day
        example: minute
    Retry-After:
      description: Sent with a 429. The length in seconds of the window that was exhausted (1, 60, 3600 or 86400); waiting that long clears it. If another window is also used up, the retry gets a new 429 with that window's length.
      schema:
        type: integer
        example: 1
    X-API-Version:
      description: Always `2`. Sent on every response except a 401 for a missing, invalid or expired bearer token, and a 429.
      schema:
        type: string
        example: '2'
    Sunset:
      description: 'RFC 8594 Sunset header. Present only on deprecated endpoints. Value is an HTTP-date indicating when the
        endpoint will be removed. Example: `Sunset: Sat, 14 Oct 2026 23:59:59 GMT`. Partners should treat receipt of this
        header as a 6-month migration notice.

        '
      schema:
        type: string
        example: Sat, 14 Oct 2026 23:59:59 GMT
  securitySchemes:
    BearerAuth:
      type: http
      scheme: bearer
      bearerFormat: JWT
      description: 'JWT Bearer token obtained from `POST /v2/api/authentication/token/api-key`.


        **Lifetime:** ~24 hours (86,399 seconds). Cache the token and reuse it. Re-authenticate 5 minutes before expiry.


        **Scoping:** API key tokens are scoped by the key''s company and its action set; see [Token scoping](/v2/authentication#token-scoping).


        **No refresh endpoint** — re-authenticate with your API key when the token expires.

        '
paths:
  /v2/api/authentication/token/api-key:
    post:
      summary: API key token (V2)
      description: Exchange a company API key for a signed JWT. This endpoint is not rate-limited.
      security: []
      requestBody:
        required: true
        content:
          application/x-www-form-urlencoded:
            schema:
              type: object
              required:
              - apiKey
              properties:
                apiKey:
                  type: string
                  example: EXAMPLE-api-key-do-not-use-0123456789abcdEF
      responses:
        '200':
          description: Successful response
          content:
            application/json:
              example:
                access_token: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
                token_type: bearer
                expires_in: 86399
        '401':
          description: '**401 Unauthorized** — The API key is unknown, deleted, or not a valid key.'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ApiError'
              example:
                type: AUTHENTICATION
                code: INVALID_CREDENTIALS
                description: The provided API key is invalid or does not exist.
                requestId: '0HNP2MAV4BUCB:00000003'
                docUrl: https://docs.telemax.com.au/errors/invalid-credentials
        '403':
          description: '**403 Forbidden** — The company the API key belongs to is suspended or otherwise not active, or a company above it is.'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ApiError'
              example:
                type: AUTHORIZATION
                code: COMPANY_SUSPENDED
                description: The company this API key belongs to is not active.
                requestId: '0HNP2MAV4BUCB:00000004'
                docUrl: https://docs.telemax.com.au/errors/company-suspended
        '422':
          description: '**422 Unprocessable Entity** — `apiKey` is missing, or the body is not a form (`application/x-www-form-urlencoded` or `multipart/form-data`).'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ApiError'
              example:
                type: VALIDATION_ERROR
                code: INVALID_INPUT
                description: One or more route or query parameters could not be parsed.
                requestId: '0HNP2MAV4BUCC:00000001'
                docUrl: https://docs.telemax.com.au/errors/invalid-input
        '500':
          description: '**500 Internal Server Error** — The server could not issue a token, for example because the company status could not be read. Include the requestId when contacting support.'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ApiError'
              example:
                type: SERVER_ERROR
                code: INTERNAL_SERVER_ERROR
                description: An unexpected error occurred. Please try again later.
                requestId: '0HNP2MAV4BUCC:00000002'
                docUrl: https://docs.telemax.com.au/errors/internal-server-error
  /v2/api/companies:
    get:
      summary: List companies (V2)
      description: >-
        Returns a paginated list of the token's company and its sub-companies, sub-companies first and the
        token's company (level 0) last. Not every listed company can be used with other endpoints; see
        [Token scoping](/v2/authentication#token-scoping).
        The `utcOffset` field is a .NET TimeSpan string — positive offsets have no sign prefix
        (e.g. `"10:00:00"` for UTC+10), negative offsets include a `-` prefix (e.g. `"-05:00:00"` for UTC-5).
      parameters:
      - name: page
        in: query
        schema:
          type: integer
          default: 1
      - name: pageSize
        in: query
        schema:
          type: integer
          default: 50
      responses:
        '200':
          description: Successful response
          content:
            application/json:
              example:
                dateTimeFormat: dd/MM/yyyy
                timezone: Australia/Brisbane
                utcOffset: '10:00:00'
                items:
                - id: 34
                  name: Acme Sydney
                  parentId: 12
                  showCommands: false
                  level: 1
                  apiKey: null
                - id: 12
                  name: Acme Fleet
                  parentId: null
                  showCommands: false
                  level: 0
                  apiKey: null
                totalResults: 2
                lastResultIndex: 2
                currentPage: 1
                numberOfPages: 1
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/InternalServerError'
  /v2/api/companies/{id}/vehicles/last-position:
    get:
      summary: Fleet last positions (V2)
      description: Last known position for every vehicle mapped to the company. A sub-company's vehicle appears only if it is also mapped to this company.
      parameters:
      - name: id
        in: path
        required: true
        schema:
          type: integer
        example: 12
      - name: checkVehicleHandlerState
        in: query
        schema:
          type: boolean
          default: false
        example: false
        description: Optional. On the current release this flag has no dependable effect; leave it `false`.
      - name: page
        in: query
        schema:
          type: integer
          default: 1
      - name: pageSize
        in: query
        schema:
          type: integer
          default: 50
      responses:
        '200':
          description: Successful response
          content:
            application/json:
              example:
                items:
                - &id004
                  utcTime: '2026-04-28T06:14:19'
                  userTime: '2026-04-28T16:14:19'
                  userTimeFormatted: '28/04/2026 04:14:19 PM'
                  lat: -33.8712
                  lng: 151.2065
                  speed: 0
                  course: 142
                  ignition: false
                  odometer: 45321.2
                  voltage: 12.6
                  fatigue: '00:26:11.5268750'
                  engineHours: 0
                  satelliteCoverage: Excellent
                  satelliteCoverageRawValue: 12
                  networkCoverage: Fair
                  networkCoverageRawValue: 18
                  deviceId: 88421
                  ignitionTime: 1590.0
                  deviceName: Delivery Van 07
                  imei: '353148090123456'
                  vin: 1HGBH41JXMN109186
                  startMovingTime: '2026-04-28T05:22:31'
                  startMovingTimeUser: '2026-04-28T15:22:31'
                  lastMovementTime: '2026-04-28T05:48:05'
                  lastMovementTimeUser: '2026-04-28T15:48:05'
                  lastMovementTimeUserFormatted: '28/04/2026 03:48:05 PM'
                  drivingTime: 3108
                  fuelLevel: 64
                  fuelVolume: 51.2
                  batteryLevel: null
                  chargerPower: null
                  chargingEnergy: null
                  batteryRange: null
                  isDoorLocked: true
                  address: 14, George St, Haymarket, Sydney, NSW, AUS
                  engineEnabled: true
                  timeElapsed: 245.12
                  startedTime: '2026-04-28T06:14:22'
                  endTime: '2026-04-28T06:14:22'
                  isOnline: true
                totalResults: 7
                lastResultIndex: 1
                currentPage: 1
                numberOfPages: 7
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          description: '**404 Not Found** — Company does not exist or is not accessible with this token.'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ApiError'
              example:
                type: RESOURCE
                code: NOT_FOUND
                description: Company was not found.
                requestId: 0HNLT5NG7N2DK:00000001
                docUrl: https://docs.telemax.com.au/errors/not-found
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/InternalServerError'
  /v2/api/companies/{id}/vehicles/last-time-online:
    get:
      summary: Fleet last-time-online (V2)
      description: Last-seen timestamps for all vehicles in the company.
      parameters:
      - name: id
        in: path
        required: true
        schema:
          type: integer
        example: 12
      - name: page
        in: query
        schema:
          type: integer
          default: 1
      - name: pageSize
        in: query
        schema:
          type: integer
          default: 50
      responses:
        '200':
          description: Successful response
          content:
            application/json:
              example:
                items:
                - vehicleId: 88421
                  imei: '353148090123456'
                  lastTimeOnlineUtc: '2026-04-28T06:14:22'
                  secondsAgo: 4218.0
                totalResults: 1
                lastResultIndex: 1
                currentPage: 1
                numberOfPages: 1
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          description: '**404 Not Found** — The requested company does not exist or is not accessible.'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ApiError'
              example:
                type: RESOURCE
                code: NOT_FOUND
                description: Company was not found.
                requestId: 0HNLTALNU4DCQ:00000002
                docUrl: https://docs.telemax.com.au/errors/not-found
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/InternalServerError'
  /v2/api/companies/{id}/vehicles/nearest:
    get:
      summary: Nearest vehicles (V2)
      description: 'Returns the vehicles mapped to the company with their distance from a coordinate. Distances are planar degrees, not metres: `realDistance` is the straight-line distance in degrees and `distance` is that value rounded to a multiple of 100, so results are not in order of nearness.'
      parameters:
      - name: id
        in: path
        required: true
        schema:
          type: integer
        example: 12
      - name: lat
        in: query
        schema:
          type: number
          default: 0
        example: -33.8688
        description: Latitude of the reference point. Optional; defaults to 0.
      - name: lng
        in: query
        schema:
          type: number
          default: 0
        example: 151.2093
        description: Longitude of the reference point. Optional; defaults to 0.
      - name: num
        in: query
        schema:
          type: integer
        description: Maximum number of vehicles to return, applied after the radius filter. No limit when omitted.
      - name: radius
        in: query
        schema:
          type: integer
        description: 'Upper bound on the rounded `distance`, which is in degrees (not metres): vehicles with `distance` at or above `radius` are excluded. No limit when omitted.'
      - name: page
        in: query
        schema:
          type: integer
          default: 1
      - name: pageSize
        in: query
        schema:
          type: integer
          default: 50
      responses:
        '200':
          description: Successful response
          content:
            application/json:
              example:
                items:
                - vehicle: *id004
                  distance: 0
                  realDistance: 0.0036878
                totalResults: 2
                lastResultIndex: 2
                currentPage: 1
                numberOfPages: 1
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          description: '**404 Not Found** — The requested company does not exist or is not accessible.'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ApiError'
              example:
                type: RESOURCE
                code: NOT_FOUND
                description: Company was not found.
                requestId: 0HNLTALNU4DD0:00000003
                docUrl: https://docs.telemax.com.au/errors/not-found
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/InternalServerError'
  /v2/api/companies/{id}/alert-records:
    get:
      summary: Alert records (V2)
      description: 'Paginated list of triggered alert records for every company the token can read (its own and its direct sub-companies that are not cancelled), whatever `{id}` is. Only the 10 alert types 3–12; `num` applies per alert type.'
      parameters:
      - name: id
        in: path
        required: true
        schema:
          type: integer
        example: 12
      - name: from
        in: query
        schema:
          type: string
          format: date-time
        example: '2026-04-01T00:00:00Z'
        description: Records triggered strictly after this time (UTC). Optional; when omitted, every stored record is considered.
      - name: num
        in: query
        schema:
          type: integer
          minimum: 1
        example: 200
        description: 'Required in practice: omitted or 0 returns nothing, and a negative value returns 500. Applied per alert type, so only the first `num` records of the merged, newest-first list are complete.'
      - name: page
        in: query
        schema:
          type: integer
          default: 1
          minimum: 1
      - name: pageSize
        in: query
        schema:
          type: integer
          default: 50
          minimum: 1
          maximum: 100
      responses:
        '200':
          description: Successful response
          content:
            application/json:
              example:
                items:
                - id: 4126
                  utcTime: '2026-05-26T03:40:40'
                  userTime: '2026-05-26T13:40:40'
                  userTimeFormatted: 26/05/2026 01:40:40 PM
                  alertType: 7
                  description: Idle for more than 10 minutes
                  vehicleId: 51207
                  vehicleName: Delivery Van 07
                  address: Hume Hwy, Campbelltown, Sydney, NSW, AUS
                  lat: -34.065
                  lng: 150.815
                  isRead: true
                totalResults: 7
                lastResultIndex: 7
                currentPage: 1
                numberOfPages: 1
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          description: '**404 Not Found** — The requested company does not exist or is not accessible.'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ApiError'
              example:
                type: RESOURCE
                code: NOT_FOUND
                description: Company was not found.
                requestId: 0HNLTALNU4DD2:00000001
                docUrl: https://docs.telemax.com.au/errors/not-found
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/InternalServerError'
  /v2/api/companies/{id}/alerts:
    get:
      summary: Alert configurations (V2)
      description: 'Paginated list of alert definitions for `{id}` and its direct sub-companies (the scope follows `{id}`). `{id}` must be the token''s company or one of its direct, non-cancelled sub-companies. Deleted definitions and reminders are excluded.'
      parameters:
      - name: id
        in: path
        required: true
        schema:
          type: integer
        example: 12
      - name: page
        in: query
        schema:
          type: integer
          default: 1
          minimum: 1
      - name: pageSize
        in: query
        schema:
          type: integer
          default: 50
          minimum: 1
          maximum: 100
      responses:
        '200':
          description: Successful response
          content:
            application/json:
              example:
                items:
                - alertId: 3631
                  alertType: Geofence
                  companyId: 997
                  alertName: Depot geofence
                  alertDescription: Entry to or exit from the depot
                  isAllFleet: false
                  isAllTags: false
                  units:
                  - vehicleId: 22259
                    name: Fleet Vehicle 01
                    imei: '353148090765439'
                    vin: 1HGBH41J4MN100001
                  tags: []
                totalResults: 9
                lastResultIndex: 2
                currentPage: 1
                numberOfPages: 5
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          description: '**404 Not Found** — The requested company does not exist or is not accessible.'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ApiError'
              example:
                type: RESOURCE
                code: NOT_FOUND
                description: Company was not found.
                requestId: 0HNLTALNU4DD4:00000002
                docUrl: https://docs.telemax.com.au/errors/not-found
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/InternalServerError'
  /v2/api/companies/{id}/set-all-alerts-read:
    put:
      summary: Mark all alerts read (V2)
      description: 'Marks every stored alert record of the 10 types returned by Alert records as read, across every company the token can read (its own and its direct sub-companies that are not cancelled), whatever `{id}` is. No date limit; records of deleted alert definitions are included. `startDate`/`endDate` are not supported and are ignored if sent.'
      parameters:
      - name: id
        in: path
        required: true
        schema:
          type: integer
        example: 12
      responses:
        '200':
          description: '**200 OK** — Empty body. Every record of the 10 types across the token''s companies is marked as read.'
          content:
            application/json:
              example: {}
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          description: '**404 Not Found** — The requested company does not exist or is not accessible.'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ApiError'
              example:
                type: RESOURCE
                code: NOT_FOUND
                description: Company was not found.
                requestId: 0HNLTALNU4DD6:00000002
                docUrl: https://docs.telemax.com.au/errors/not-found
        '422':
          $ref: '#/components/responses/UnprocessableEntity'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/InternalServerError'
  /v2/api/vehicles/info:
    get:
      summary: Vehicle info (V2)
      description: Look up vehicle metadata by IMEI, vehicle ID, or VIN.
      parameters:
      - name: imei
        in: query
        schema:
          type: string
      - name: id
        in: query
        schema:
          type: integer
      - name: vin
        in: query
        schema:
          type: string
      responses:
        '200':
          description: Successful response
          content:
            application/json:
              schema:
                type: array
                items:
                  type: object
                  properties:
                    label:
                      type: string
                      description: Field name (e.g. "Make", "Model Year", "Engine Power (kW)")
                    value:
                      type: string
                      description: Field value (always a string, even for numeric or array values)
              example:
                - label: VIN
                  value: MPAUCS40GST011254
                - label: Vehicle ID
                  value: '28193'
                - label: Make
                  value: Isuzu
                - label: Model
                  value: MU-X
                - label: Model Year
                  value: '2025'
                - label: Product Type
                  value: Car
                - label: Body
                  value: Wagon
                - label: Drive
                  value: Rear-wheel drive
                - label: Engine Displacement (ccm)
                  value: '2999'
                - label: Engine Power (kW)
                  value: '140'
                - label: Engine Power (HP)
                  value: '188'
                - label: Fuel Type - Primary
                  value: Diesel
                - label: Engine Code
                  value: 4JJ3-TCX
                - label: Transmission
                  value: Auto
                - label: Number of Gears
                  value: '6'
                - label: Manufacturer
                  value: Isuzu Motors Company (Thailand) Limited
                - label: Manufacturer Address
                  value: 38 Moo 9 Poojaosamingprai Road, ...
                - label: Plant Country
                  value: Thailand
                - label: Make Logo
                  value: https://cdn.example.com/img/make/isuzu.svg
                - label: Engine Compression Ratio
                  value: '16.3'
                - label: Engine Cylinder Bore (mm)
                  value: '95.4'
                - label: Engine Cylinders
                  value: '4'
                - label: Engine Cylinders Position
                  value: Inline
                - label: Engine Position
                  value: Front, Longitudinal
                - label: Engine RPM
                  value: '3600'
                - label: Engine Stroke (mm)
                  value: '104.9'
                - label: Engine Torque (RPM)
                  value: '450'
                - label: Engine Turbine
                  value: Turbocharger, Intercooler
                - label: Valve Train
                  value: DOHC
                - label: Fuel Capacity (l)
                  value: '80'
                - label: Fuel System
                  value: Diesel Commonrail
                - label: Valves per Cylinder
                  value: '4'
                - label: Number of Axles
                  value: '2'
                - label: Number of Doors
                  value: '5'
                - label: Number of Seats
                  value: '7'
                - label: Power Steering
                  value: Electric Steering
                - label: Steering Type
                  value: Steering rack and pinion
                - label: Wheelbase (mm)
                  value: '2855'
                - label: Width (mm)
                  value: '1870'
                - label: Ride Height (mm)
                  value: '235'
                - label: Track Front (mm)
                  value: '1570'
                - label: Track Rear (mm)
                  value: '1570'
                - label: Weight Empty (kg)
                  value: '2800'
                - label: ABS
                  value: '1'
                - label: Check Digit
                  value: G
                - label: Sequential Number
                  value: '011254'
                - label: Body Type
                  value: Suv
                - label: Fuel Type
                  value: Diesel
                - label: Engine Size
                  value: '3'
                - label: Colour
                  value: White
                - label: Fuel Capacity
                  value: '80'
                - label: Number Plate
                  value: ABC123
                - label: Registration State
                  value: Queensland
                - label: Registration Country
                  value: Australia
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          description: '**404 Not Found** — The vehicle does not exist, is not in your companies, or has no VIN. The `imei` lookup is not limited to your companies, so an IMEI shared with another company''s vehicle can also give 404.'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ApiError'
              example:
                type: RESOURCE
                code: NOT_FOUND
                description: Vehicle was not found.
                requestId: 0HNLTALNU4DDC:00000001
                docUrl: https://docs.telemax.com.au/errors/not-found
        '422':
          description: '**422 Unprocessable Entity** — Not exactly one of `imei`, `id` and `vin` was supplied.'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ApiError'
              example:
                type: VALIDATION_ERROR
                code: INVALID_INPUT
                description: Exactly one of imei, id, or vin must be provided.
                requestId: 0HNP2MAV4BUG4:00000001
                docUrl: https://docs.telemax.com.au/errors/invalid-input
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/InternalServerError'
  /v2/api/vehicles/list:
    post:
      summary: List vehicles (V2)
      description: 'Returns the last known position for a specific set of vehicles.
        Items come back in the vehicles'' internal order, not in the order of `vehicleIds`.


        **`vehicleIds` is required** and must contain between 1 and 50 `deviceId` values.
        Passing an empty array or omitting the field returns **422**.

        '
      parameters:
      - name: page
        in: query
        schema:
          type: integer
          default: 1
      - name: pageSize
        in: query
        schema:
          type: integer
          default: 50
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
              - vehicleIds
              properties:
                vehicleIds:
                  type: array
                  items:
                    type: integer
                  minItems: 1
                  maxItems: 50
                  description: 'List of public vehicle IDs (`deviceId` values) to query. Must contain between 1 and 50 entries. Returns **422** if empty or omitted.'
            example:
              vehicleIds:
              - 88421
              - 88422
      responses:
        '200':
          description: Successful response
          content:
            application/json:
              example:
                items:
                - *id004
                totalResults: 1
                lastResultIndex: 1
                currentPage: 1
                numberOfPages: 1
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          description: '**403 Forbidden** — The company is suspended or otherwise not active, or a company above it is. Vehicle IDs that are unknown or not in your companies are left out of the 200 response.'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CompanySuspendedError'
              example:
                code: CompanySuspended
                message: The company has been suspended. Please contact your administrator.
        '415':
          description: '**415 Unsupported Media Type** — The request was sent with no `Content-Type`, or one that is not JSON (send `application/json`). The body is ASP.NET''s standard problem-details object, not the API''s usual error body.'
          content:
            application/problem+json:
              example:
                type: https://tools.ietf.org/html/rfc9110#section-15.5.16
                title: Unsupported Media Type
                status: 415
                traceId: 00-85fcff920c4df916c4e5aebe4e65672f-81b3733865a41fdf-00
        '422':
          description: '**422 Unprocessable Entity** — `vehicleIds` is empty or contains more than 50 entries, the body is empty, `page` is less than 1, or `pageSize` is less than 1 or more than 500.'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ApiError'
              examples:
                emptyVehicleIds:
                  summary: vehicleIds must have at least 1 entry
                  value:
                    type: VALIDATION_ERROR
                    code: INVALID_INPUT
                    description: VehicleIds must contain at least 1 entry.
                    requestId: 0HNLTALNU4DCU:00000001
                    docUrl: https://docs.telemax.com.au/errors/invalid-input
                tooManyVehicleIds:
                  summary: vehicleIds must not exceed 50
                  value:
                    type: VALIDATION_ERROR
                    code: INVALID_INPUT
                    description: VehicleIds must not contain more than 50 entries.
                    requestId: 0HNLTALNU4DCU:00000002
                    docUrl: https://docs.telemax.com.au/errors/invalid-input
                invalidPage:
                  summary: Page must be 1 or greater
                  value:
                    type: VALIDATION_ERROR
                    code: INVALID_INPUT
                    description: page must be 1 or greater.
                    requestId: 0HNLTALNU4DCU:00000003
                    docUrl: https://docs.telemax.com.au/errors/invalid-input
                pageSizeTooLarge:
                  summary: pageSize must not exceed 500
                  value:
                    type: VALIDATION_ERROR
                    code: INVALID_INPUT
                    description: pageSize cannot exceed 500 for this endpoint.
                    requestId: 0HNP2MAV4BUG4:00000002
                    docUrl: https://docs.telemax.com.au/errors/invalid-input
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/InternalServerError'
  /v2/api/vehicles/{id}/last-position:
    get:
      summary: Last position (V2)
      description: Returns the last known position for a single vehicle. The body is empty when the vehicle has no stored position, or has not yet completed an ignition-on or ignition-off period.
      parameters:
      - name: id
        in: path
        required: true
        schema:
          type: integer
        example: 88421
      responses:
        '200':
          description: Successful response
          content:
            application/json:
              example:
                utcTime: '2026-04-29T06:14:08'
                userTime: '2026-04-29T16:14:08'
                userTimeFormatted: '29/04/2026 04:14:08 PM'
                lat: -33.8761
                lng: 151.2049
                speed: 46
                course: 188
                ignition: true
                odometer: 45327.9
                voltage: 14.1
                fatigue: '00:00:00'
                engineHours: 0
                satelliteCoverage: Good
                satelliteCoverageRawValue: 9
                networkCoverage: Good
                networkCoverageRawValue: 22
                deviceId: 88421
                ignitionTime: 14.3829
                deviceName: Delivery Van 07
                imei: '353148090123456'
                vin: 1HGBH41JXMN109186
                startMovingTime: '2026-04-29T06:01:10'
                startMovingTimeUser: '2026-04-29T16:01:10'
                lastMovementTime: '2026-04-29T06:11:37'
                lastMovementTimeUser: '2026-04-29T16:11:37'
                lastMovementTimeUserFormatted: '29/04/2026 04:11:37 PM'
                drivingTime: 778
                fuelLevel: 63
                fuelVolume: 50.4
                batteryLevel: null
                chargerPower: null
                chargingEnergy: null
                batteryRange: null
                isDoorLocked: true
                address: 14, George St, Haymarket, Sydney, NSW, AUS
                engineEnabled: true
                timeElapsed: 312.47
                startedTime: '2026-04-29T06:14:22'
                endTime: '2026-04-29T06:14:22'
                isOnline: false
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          description: '**404 Not Found** — The requested vehicle does not exist or is not accessible.'
          headers:
            X-RateLimit-Limit:
              $ref: '#/components/headers/X-RateLimit-Limit'
            X-RateLimit-Remaining:
              $ref: '#/components/headers/X-RateLimit-Remaining'
            X-RateLimit-Reset:
              $ref: '#/components/headers/X-RateLimit-Reset'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ApiError'
              example:
                type: RESOURCE
                code: NOT_FOUND
                description: Vehicle was not found.
                requestId: '0HNP2MAV4BUCC:00000003'
                docUrl: https://docs.telemax.com.au/errors/not-found
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/InternalServerError'
  /v2/api/vehicles/{id}/last-online:
    get:
      summary: Last online (V2)
      description: Returns the last time a vehicle was online and elapsed seconds.
      parameters:
      - name: id
        in: path
        required: true
        schema:
          type: integer
        example: 88421
      responses:
        '200':
          description: Successful response
          content:
            application/json:
              example:
                lastTimeOnlineUtc: '2026-04-28T06:14:22'
                secondsAgo: 4218.0
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          description: '**404 Not Found** — The requested vehicle does not exist or is not accessible.'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ApiError'
              example:
                type: RESOURCE
                code: NOT_FOUND
                description: Vehicle was not found.
                requestId: 0HNLTALNU4DCS:00000001
                docUrl: https://docs.telemax.com.au/errors/not-found
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/InternalServerError'
  /v2/api/vehicles/{id}/positions:
    get:
      summary: Position history (V2)
      description: 'Paginated historical position records for a vehicle within a date range. Both `from` and `to` are required — omitting either returns **422**.'
      parameters:
      - name: id
        in: path
        required: true
        schema:
          type: integer
        example: 88421
      - name: from
        in: query
        required: true
        schema:
          type: string
          format: date-time
        example: '2026-04-01T00:00:00Z'
        description: Start of the date range (inclusive). Required — returns 422 if missing.
      - name: to
        in: query
        required: true
        schema:
          type: string
          format: date-time
        example: '2026-04-02T00:00:00Z'
        description: End of the date range (exclusive — a record at exactly `to` is not returned). Required — returns 422 if missing.
      - name: page
        in: query
        schema:
          type: integer
          default: 1
      - name: pageSize
        in: query
        schema:
          type: integer
          default: 50
      responses:
        '200':
          description: Successful response
          content:
            application/json:
              example:
                items:
                - utcTime: '2026-04-01T00:14:15'
                  userTime: '2026-04-01T11:14:15'
                  lat: -27.443087
                  lng: 153.019897
                  speed: 0
                  ignition: false
                  odometer: 45103.6
                  internalBatteryVoltage: 12.07
                  deviceId: 88421
                  deviceName: Delivery Van 07
                  fuelLevel: null
                  fuelVolume: null
                  batteryLevel: null
                  chargerPower: null
                  chargingEnergy: null
                  batteryRange: null
                totalResults: 35
                lastResultIndex: 2
                currentPage: 1
                numberOfPages: 18
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          description: '**404 Not Found** — The requested vehicle does not exist or is not accessible.'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ApiError'
              example:
                type: RESOURCE
                code: NOT_FOUND
                description: Vehicle was not found.
                requestId: 0HNLTALNU4DD9:00000002
                docUrl: https://docs.telemax.com.au/errors/not-found
        '422':
          $ref: '#/components/responses/UnprocessableEntity'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/InternalServerError'
  /v2/api/vehicles/{id}/engine-codes:
    get:
      summary: Engine codes (V2)
      description: 'Engine codes newly reported in the vehicle''s latest engine-fault record (not the full list of currently active codes), with AI-generated analysis where available, severity and detection location. A geocoding failure returns 500.'
      parameters:
      - name: id
        in: path
        required: true
        schema:
          type: integer
        example: 88421
      - name: page
        in: query
        schema:
          type: integer
          default: 1
      - name: pageSize
        in: query
        schema:
          type: integer
          default: 50
      responses:
        '200':
          description: Successful response
          content:
            application/json:
              example:
                items:
                - code: P0420
                  description: Catalyst System Efficiency Below Threshold (Bank 1)
                  whyThisMatters:
                  - This fault indicates your catalytic converter is no longer processing exhaust gases effectively, which means your vehicle is producing higher levels of harmful emissions than permitted.
                  - If left unaddressed, the engine management system may enter a reduced-power mode, leading to noticeably worse fuel economy and throttle response.
                  - In some regions this fault will cause the vehicle to fail an emissions inspection.
                  possibleCauses:
                  - The catalytic converter itself has degraded or been poisoned — commonly caused by using leaded fuel, coolant leaks into the exhaust, or high-mileage wear.
                  - A faulty upstream or downstream oxygen sensor is reporting incorrect readings, making a healthy converter appear to be failing.
                  - An exhaust leak upstream of the converter is allowing unmetered air to enter, skewing the sensor readings.
                  recommendedActions:
                  - Begin by scanning for any additional fault codes, particularly oxygen sensor faults (P0130–P0167), as these should be resolved first before condemning the catalytic converter.
                  - Inspect the exhaust system for any leaks between the engine and the catalytic converter, and repair any found.
                  - If the oxygen sensors test within spec and there are no exhaust leaks, have the catalytic converter tested for flow restriction and converter efficiency under load — replacement is likely required.
                  severity: Medium
                  detectedAt: '2026-04-27T14:30:00'
                  detectedAtFormatted: 27/04/2026 02:30:00 PM
                  location:
                    address: 14, George St, Haymarket, Sydney, NSW, AUS
                    latitude: -33.8795
                    longitude: 151.205
                totalResults: 1
                lastResultIndex: 1
                currentPage: 1
                numberOfPages: 1
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          description: '**404 Not Found** — The requested vehicle does not exist or is not accessible.'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ApiError'
              example:
                type: RESOURCE
                code: NOT_FOUND
                description: Vehicle was not found.
                requestId: 0HNLTALNU4DDG:00000002
                docUrl: https://docs.telemax.com.au/errors/not-found
        '422':
          $ref: '#/components/responses/UnprocessableEntity'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/InternalServerError'
  /v2/api/vehicles/{imei}/device-ids:
    get:
      summary: Device ID from IMEI (V2)
      description: Resolves a device IMEI to the public vehicle ID and name.
      parameters:
      - name: imei
        in: path
        required: true
        schema:
          type: string
        example: '353148090123456'
      responses:
        '200':
          description: Successful response
          content:
            application/json:
              example:
                vehicleId: 88421
                vehicleName: Delivery Van 07
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          description: '**404 Not Found** — IMEI does not exist or is not accessible with this token.'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ApiError'
              example:
                type: RESOURCE
                code: NOT_FOUND
                description: Vehicle was not found.
                requestId: 0HNLTALNU4DDE:00000004
                docUrl: https://docs.telemax.com.au/errors/not-found
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/InternalServerError'
  /v2/api/vehicles/{id}/name:
    put:
      summary: Rename vehicle (V2)
      description: Updates the display name of a vehicle.
      parameters:
      - name: id
        in: path
        required: true
        schema:
          type: integer
        example: 88421
      - name: newVehicleName
        in: query
        required: true
        schema:
          type: string
        example: Delivery Van 07
      responses:
        '200':
          description: '**200 OK** — Empty body. Vehicle name updated successfully.'
          content:
            application/json:
              example: {}
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          description: '**404 Not Found** — The requested vehicle does not exist or is not accessible.'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ApiError'
              example:
                type: RESOURCE
                code: NOT_FOUND
                description: Vehicle was not found.
                requestId: 0HNLTALNU4DDJ:00000003
                docUrl: https://docs.telemax.com.au/errors/not-found
        '422':
          description: '**422 Unprocessable Entity** — `newVehicleName` is missing or empty.'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ApiError'
              example:
                type: VALIDATION_ERROR
                code: INVALID_INPUT
                description: One or more route or query parameters could not be parsed.
                requestId: 0HNLTALNU4DDJ:00000004
                docUrl: https://docs.telemax.com.au/errors/invalid-input
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/InternalServerError'
  /v2/api/vehicles/{id}/odometer:
    get:
      summary: Get odometer (V2)
      description: Returns the vehicle's odometer source and the odometer reading read live from Flespi telemetry. For the platform's own odometer, use `odometer` from Last position.
      parameters:
      - name: id
        in: path
        required: true
        schema:
          type: integer
        example: 88421
      responses:
        '200':
          description: '**200 OK** — Odometer source and Flespi''s live mileage reading.'
          content:
            application/json:
              schema:
                type: object
                properties:
                  odometerSource:
                    type: string
                    enum:
                    - CAN
                    - GPS
                    description: '`CAN` when the latest odometer reading came from the CAN bus, otherwise `GPS` (including when the source is unknown). Always present.'
                  odometerValue:
                    type: number
                    nullable: true
                    description: 'Mileage (km) read live from Flespi: `can.vehicle.mileage` when `odometerSource` is `CAN`, otherwise Flespi''s `vehicle.mileage`. Always `null` for vehicles not on the Flespi data provider, and also `null` when Flespi has no value or the call fails.'
              example:
                odometerSource: GPS
                odometerValue: 154200.5
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          description: '**404 Not Found** — The requested vehicle does not exist or is not accessible.'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ApiError'
              example:
                type: RESOURCE
                code: NOT_FOUND
                description: Vehicle was not found.
                requestId: 0HNLTALNU4DDI:00000002
                docUrl: https://docs.telemax.com.au/errors/not-found
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/InternalServerError'
    put:
      summary: Update odometer (V2)
      description: 'Sets the GPS odometer reading for a vehicle by sending it to the device as a command. May return **409** when the vehicle''s odometer comes from the CAN bus, which cannot be overridden; the check is skipped when the server holds no current state for the vehicle.'
      parameters:
      - name: id
        in: path
        required: true
        schema:
          type: integer
        example: 88421
      - name: odometer
        in: query
        schema:
          type: number
          minimum: 0
        example: 154200
        description: 'New odometer value in kilometres. Must be ≥ 0. **Omitting it sets the odometer to 0** — always send it.'
      responses:
        '200':
          description: '**200 OK** — Empty body. Returned even when the command could not be sent to the device; it does not confirm that the device received or applied the value.'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          description: '**404 Not Found** — The requested vehicle does not exist or is not accessible.'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ApiError'
              example:
                type: RESOURCE
                code: NOT_FOUND
                description: Vehicle was not found.
                requestId: 0HNLTALNU4DDI:00000002
                docUrl: https://docs.telemax.com.au/errors/not-found
        '422':
          description: '**422 Unprocessable Entity** — `odometer` is negative or not a number. A negative value is checked first, so an unknown vehicle ID with a negative value also gets 422.'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ApiError'
              example:
                type: VALIDATION_ERROR
                code: INVALID_INPUT
                description: Odometer value must be greater than or equal to zero.
                requestId: 0HNLTALNU4DDH:00000002
                docUrl: https://docs.telemax.com.au/errors/invalid-input
        '409':
          description: '**409 Conflict** — May be returned when the vehicle''s odometer is provided by the CAN bus and cannot be manually updated. Not guaranteed: the check is skipped when the server holds no current state for the vehicle.'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ApiError'
              example:
                type: RESOURCE_CONFLICT
                code: ODOMETER_SOURCE_CONFLICT
                description: The vehicle's odometer is provided by the CAN bus and cannot be manually updated.
                docUrl: https://docs.telemax.com.au/errors/odometer-source-conflict
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/InternalServerError'
  /v2/api/vehicles/{id}/odometer/source:
    put:
      summary: Update odometer source (V2)
      description: 'Sets the odometer source for a vehicle to `CAN` or `GPS`. Returns **409** if the device does not support CAN odometer.'
      parameters:
      - name: id
        in: path
        required: true
        schema:
          type: integer
        example: 88421
      - name: source
        in: query
        required: true
        schema:
          type: string
          enum:
          - CAN
          - GPS
        example: GPS
        description: Desired odometer source. Must be `CAN` or `GPS` (case-insensitive).
      responses:
        '200':
          description: '**200 OK** — Empty body. Odometer source updated successfully.'
          content:
            application/json:
              example: {}
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          description: '**404 Not Found** — The requested vehicle does not exist or is not accessible.'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ApiError'
              example:
                type: RESOURCE
                code: NOT_FOUND
                description: Vehicle was not found.
                requestId: 0HNLTALNU4DDI:00000002
                docUrl: https://docs.telemax.com.au/errors/not-found
        '409':
          description: '**409 Conflict** — The device does not support the specified odometer source (e.g. requesting `CAN` on a device without CAN bus support).'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ApiError'
              example:
                type: RESOURCE_CONFLICT
                code: ODOMETER_SOURCE_NOT_SUPPORTED
                description: The device does not support the specified value as a valid odometer source.
                docUrl: https://docs.telemax.com.au/errors/odometer-source-unsupported
        '422':
          description: '**422 Unprocessable Entity** — `source` is not `CAN` or `GPS` (message shown). A missing `source` returns the generic "One or more route or query parameters could not be parsed."'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ApiError'
              example:
                type: VALIDATION_ERROR
                code: INVALID_INPUT
                description: Source must be 'CAN' or 'GPS'.
                requestId: 0HNLUDQ8PSO21:00000003
                docUrl: https://docs.telemax.com.au/errors/invalid-input
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/InternalServerError'
  /v2/api/battery-health:
    get:
      summary: Battery health (V2)
      description: 'Battery health scores for the vehicles mapped to the token''s own company (sub-companies'' vehicles only if also mapped to it), for the company''s latest scoring date only.'
      parameters:
      - name: pageNumber
        in: query
        schema:
          type: integer
          default: 1
        description: Page number, 1 or more. Returned as `page` in the response. A lower value returns 500 when the company has scored vehicles (with sortBy 7, the first page instead).
      - name: pageSize
        in: query
        schema:
          type: integer
          default: 25
        description: Items per page; no maximum. Returned as `pageSize` in the response. A negative value returns 500 when the company has scored vehicles (with sortBy 7, no items instead).
      - name: searchText
        in: query
        schema:
          type: string
        description: Case-insensitive substring match on the vehicle name or the device identifier (the IMEI for Telemax and Flespi devices). `%` and `_` act as wildcards.
      - name: filter
        in: query
        schema:
          type: integer
          default: 0
        description: 'Band filter: 0 = all, 1 = Critical, 2 = Fair and Poor, 3 = Good and Excellent. Any other value returns all.'
      - name: sortBy
        in: query
        schema:
          type: integer
          default: 0
        description: 'Sort field: 0 = internal vehicle ID (sortDescending ignored), 1 = vehicle name, 2 = score, 3 = band (alphabetical), 4 = daysToReplace, 5 = currentVoltage, 6 = scoredOn (all equal, order undefined), 7 = badge severity (ascending puts the most severe first; badgeSummary is empty). Any other value sorts by internal vehicle ID, like 0. All other sorts can tie (7 on equal severity and score), and tied items can repeat or be skipped between pages.'
      - name: sortDescending
        in: query
        schema:
          type: boolean
          default: false
        description: When true, reverses the sort. Ignored for sortBy 0 and for values outside 0–7.
      - name: badgeFilter
        in: query
        schema:
          type: integer
          default: 0
        description: 'Only vehicles with this badge: 0 = no filter, 1 = DEEP_DISCHARGE, 2 = ALTERNATOR (never emitted), 3 = PARK_DRAIN, 4 = TREND_DOWN, 5 = CHARGE_DOWN, 6 = SHORT_TRIPS, 7 = REPLACE, 8 = HEALTHY. Any other value returns 500 when the company has scored vehicles.'
      responses:
        '200':
          description: '**200 OK** — A fleet summary and a page of per-vehicle battery health scores, badges, component scores and daily voltages.'
          content:
            application/json:
              schema:
                type: object
                properties:
                  summary:
                    type: object
                    description: Counts and average score over every vehicle scored on the latest date; ignores filter, searchText and badgeFilter.
                    properties:
                      healthyCount:
                        type: integer
                        description: Vehicles with an Excellent or Good band.
                      warningCount:
                        type: integer
                        description: Vehicles with a Fair or Poor band.
                      criticalCount:
                        type: integer
                        description: Vehicles with a Critical band.
                      avgScore:
                        type: number
                        description: Average score (0–100) of the scored vehicles, not rounded; 0 when there are none.
                  items:
                    type: array
                    description: Paginated list of per-vehicle battery health records.
                    items:
                      type: object
                      properties:
                        vehicleId:
                          type: integer
                          description: Internal vehicle ID. It can differ from `deviceId`; match battery rows to position data on `imei`.
                        vehicleName:
                          type: string
                          description: Display name of the vehicle.
                        imei:
                          type: string
                          description: Device IMEI for Telemax and Flespi devices; null for other devices.
                        currentVoltage:
                          type: number
                          description: Battery voltage (V) from the vehicle's live state; null when unknown.
                        isEngineOn:
                          type: boolean
                          description: Whether the engine is running, from the vehicle's live state; null when unknown.
                        score:
                          type: integer
                          description: Battery health score, 0–100, the sum of the six component scores.
                        band:
                          type: string
                          enum:
                          - Excellent
                          - Good
                          - Fair
                          - Poor
                          - Critical
                          description: Health band. Excellent is given only to a battery detected as newly replaced.
                        scoredOn:
                          type: string
                          format: date
                          description: The company's latest scoring date (YYYY-MM-DD), the same for every item.
                        daysToReplace:
                          type: integer
                          description: Estimated days until the battery should be replaced, 14–90. A forecast below 14 days is reported as 14 and forces the band to Critical.
                        badges:
                          type: array
                          description: Badge IDs. HEALTHY when no other badge applies; REPLACE appears on its own.
                          items:
                            type: string
                            enum:
                            - DEEP_DISCHARGE
                            - PARK_DRAIN
                            - TREND_DOWN
                            - CHARGE_DOWN
                            - SHORT_TRIPS
                            - REPLACE
                            - HEALTHY
                        badgeSummary:
                          type: string
                          description: One sentence summarising the badges. Empty when sortBy is 7.
                        restScore:
                          type: integer
                          description: Resting-voltage component, 0–30.
                        crankScore:
                          type: integer
                          description: Cranking-voltage component, 0–25.
                        trendScore:
                          type: integer
                          description: 30-day voltage-trend component, 0–12.
                        decayScore:
                          type: integer
                          description: Parked-decay component, 0–13.
                        chargeScore:
                          type: integer
                          description: Charge-acceptance component, 0–10.
                        latestScore:
                          type: integer
                          description: Latest-reading component, 0–10.
                        last90DaysVoltage:
                          type: array
                          description: Daily average external battery voltage over the last 90 days, by UTC date, oldest first. Days without readings are omitted.
                          items:
                            type: object
                            properties:
                              date:
                                type: string
                                format: date
                                description: Calendar date (YYYY-MM-DD, UTC).
                              voltage:
                                type: number
                                description: Average voltage for that day in volts; not rounded (single-precision noise, e.g. 12.609999656677246).
                  totalCount:
                    type: integer
                    description: Number of vehicles matching filter, searchText and badgeFilter, before pagination.
                  page:
                    type: integer
                    description: Current page number (1-based).
                  pageSize:
                    type: integer
                    description: Number of results returned per page.
              example:
                summary:
                  healthyCount: 18
                  warningCount: 4
                  criticalCount: 1
                  avgScore: 81.30434782608695
                items:
                - vehicleId: 51207
                  vehicleName: Delivery Van 07
                  imei: '353148090123456'
                  currentVoltage: 12.62
                  isEngineOn: false
                  score: 84
                  band: Good
                  scoredOn: '2026-10-05'
                  daysToReplace: 90
                  badges:
                  - HEALTHY
                  badgeSummary: All signals within normal range.
                  restScore: 25
                  crankScore: 20
                  trendScore: 12
                  decayScore: 13
                  chargeScore: 7
                  latestScore: 7
                  last90DaysVoltage:
                  - date: '2026-10-01'
                    voltage: 12.609999656677246
                  - date: '2026-10-02'
                    voltage: 12.579999923706055
                  - date: '2026-10-04'
                    voltage: 12.630000114440918
                - vehicleId: 51318
                  vehicleName: Service Ute 12
                  imei: null
                  currentVoltage: 12.08
                  isEngineOn: false
                  score: 52
                  band: Poor
                  scoredOn: '2026-10-05'
                  daysToReplace: 31
                  badges:
                  - PARK_DRAIN
                  - CHARGE_DOWN
                  - SHORT_TRIPS
                  badgeSummary: Losing charge while parked; lots of short trips — schedule a load test.
                  restScore: 19
                  crankScore: 13
                  trendScore: 12
                  decayScore: 0
                  chargeScore: 4
                  latestScore: 4
                  last90DaysVoltage:
                  - date: '2026-10-03'
                    voltage: 12.210000038146973
                  - date: '2026-10-04'
                    voltage: 12.119999885559082
                totalCount: 23
                page: 1
                pageSize: 2
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '422':
          $ref: '#/components/responses/UnprocessableEntity'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/InternalServerError'
  /v2/api/replay/{id}:
    get:
      summary: Trip replay (V2)
      description: 'Paginated replay of completed trips that overlap the window (`to` inclusive). Only ignition-on trips of at least 300 m; points are clipped to the window; `encoded` and `url` cover the first 400 points inside the window.'
      parameters:
      - name: id
        in: path
        required: true
        schema:
          type: integer
        example: 88421
      - name: from
        in: query
        schema:
          type: string
          format: date-time
        example: '2026-04-01T00:00:00Z'
        description: Range start (UTC). Optional; omit it to get every trip up to `to`.
      - name: to
        in: query
        schema:
          type: string
          format: date-time
        example: '2026-04-02T00:00:00Z'
        description: Range end (UTC), inclusive. Omitting only `to` returns 422; omitting both returns an empty list.
      - name: page
        in: query
        schema:
          type: integer
          default: 1
      - name: pageSize
        in: query
        schema:
          type: integer
          default: 50
          maximum: 500
      responses:
        '200':
          description: Successful response
          content:
            application/json:
              example:
                items:
                - distance: 14
                  duration: '00:20:22'
                  startAddress: 31, Diamantina Cir, Karalee, Brisbane, Qld, AUS
                  endAddress: 5, Gordon St, Ipswich, Brisbane, Qld, AUS
                  startTimeUser: 05/12/2026 08:09:09
                  endTimeUser: 05/12/2026 08:29:31
                  encoded: _p~iF~ps|U_ulLnnqC_mqNvxq`@
                  url: https://maps.googleapis.com/maps/api/staticmap?...
                  points:
                  - lat: -27.536263
                    lng: 152.840733
                    timeUtc: '2026-05-12T01:09:09'
                    speed: 0
                    direction: 260
                    ignition: true
                    timeUser: '2026-05-12T08:09:09'
                    odo: 47465
                    fuelLevel: 92
                    fuelVolume: null
                    batteryLevel: null
                    chargerPower: null
                    chargingEnergy: null
                    batteryRange: null
                totalResults: 31
                lastResultIndex: 1
                currentPage: 1
                numberOfPages: 31
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          description: '**404 Not Found** — The requested vehicle does not exist or is not accessible.'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ApiError'
              example:
                type: RESOURCE
                code: NOT_FOUND
                description: Vehicle was not found.
                requestId: 0HNLTALNU4DDO:00000003
                docUrl: https://docs.telemax.com.au/errors/not-found
        '422':
          $ref: '#/components/responses/UnprocessableEntity'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/InternalServerError'
  /v2/api/alerts/{id}/{date}/update-read-status:
    put:
      summary: Update alert read status (V2)
      description: 'Marks one alert record as read or unread. The record is matched exactly on `{id}` and `{date}`; when nothing matches the call still returns 200 and changes nothing.'
      parameters:
      - name: id
        in: path
        required: true
        schema:
          type: integer
        example: 101
        description: 'Alert definition ID: the record''s `id` in Alert records.'
      - name: date
        in: path
        required: true
        schema:
          type: string
        example: '2026-04-27T14:30:00'
        description: 'The record''s trigger time in UTC (its `utcTime`), matched exactly to the fraction of a second. `utcTime` is served without fractional seconds, so a record stored with them cannot be addressed from it.'
      - name: isRead
        in: query
        schema:
          type: boolean
          default: false
        example: true
        description: '`true` marks the record read, `false` unread. Optional: omitting it marks the record unread.'
      responses:
        '200':
          description: '**200 OK** — Empty body. Also returned when no record matched, so it does not confirm a change.'
          content:
            application/json:
              example: {}
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          description: '**404 Not Found** — The alert definition does not exist or is not in your companies. A missing record returns 200, not 404.'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ApiError'
              example:
                type: RESOURCE
                code: NOT_FOUND
                description: Alert was not found.
                requestId: 0HNLTALNU4DDM:00000003
                docUrl: https://docs.telemax.com.au/errors/not-found
        '422':
          $ref: '#/components/responses/UnprocessableEntity'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/InternalServerError'
  /v2/api/webhooks:
    get:
      summary: List webhooks (V2)
      description: Returns the webhooks that have not been deleted, active or not, of the token's companies, newest first.
      parameters:
      - name: page
        in: query
        schema:
          type: integer
          default: 1
          minimum: 1
      - name: pageSize
        in: query
        schema:
          type: integer
          default: 50
          minimum: 1
          maximum: 500
      responses:
        '200':
          description: Successful response
          content:
            application/json:
              example:
                items:
                - &id007
                  id: 42
                  url: https://webhook.site/qa-telemax-test1
                  alertIds: &id006
                  - 4261
                  isActive: false
                  isGlobal: false
                  createdAt: '2026-05-29T10:33:22'
                  warning: null
                totalResults: 12
                lastResultIndex: 2
                currentPage: 1
                numberOfPages: 6
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '422':
          description: '**422 Unprocessable Entity** — `page` or `pageSize` is not an integer (the shared validation description), `page` is below 1, or `pageSize` is outside 1–500.'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ApiError'
              example:
                type: VALIDATION_ERROR
                code: INVALID_INPUT
                description: pageSize cannot exceed 500 for this endpoint.
                requestId: 0HNLTALNU4DE0:00000001
                docUrl: https://docs.telemax.com.au/errors/invalid-input
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/InternalServerError'
    post:
      summary: Create webhook (V2)
      description: 'Creates a new webhook, owned by the token''s company, and returns the HMAC signing secret.


        Only alert configurations of some types can be linked to a webhook; see [Supported
        alert types](/v2/webhooks-overview#supported-alert-types). An alert of any other type returns 422.

        '
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
              - url
              properties:
                url:
                  type: string
                  format: uri
                  description: '`http` or `https`. The host must contain a dot or be an IP address, must resolve, and must not resolve to a loopback, private, CGNAT, link-local or multicast address, to `0.0.0.0/8` or `240.0.0.0/4`, to `::`, or to an IPv6 unique-local, 6to4 or Teredo address; otherwise the response is 422. A value that cannot be parsed as a URL is not always refused: it can be accepted, and nothing is ever delivered to it.'
                  example: https://your-server.example.com/telemax-webhook
                alertIds:
                  type: array
                  items:
                    type: integer
                  description: Alert configuration IDs to link, at most 50 distinct. Required when `isGlobal` is false; ignored when it is true. Each must be of a type that can be linked (see the endpoint description).
                  example:
                  - 101
                  - 102
                isActive:
                  type: boolean
                  default: true
                isGlobal:
                  type: boolean
                  default: false
      responses:
        '200':
          description: Successful response. `warning` is set when the linked alerts have different types.
          content:
            application/json:
              example:
                secret: 0ABFAA308801519BFA69ECBC050AACDD567D46B042C85CD08171E4F327E71981
                id: 41
                url: https://webhook.site/qa-telemax-test1
                alertIds: []
                isActive: false
                isGlobal: true
                createdAt: '2026-05-29T10:33:21'
                warning: null
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          description: '**404 Not Found** — An alert ID is unknown, deleted, or not in your companies.'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ApiError'
              example:
                type: RESOURCE
                code: NOT_FOUND
                description: Alert 99999 was not found.
                requestId: 0HNLTALNU4DDT:00000002
                docUrl: https://docs.telemax.com.au/errors/not-found
        '422':
          description: '**422 Unprocessable Entity** — The request failed validation (a missing `url` or one the `url` rules refuse, or empty `alertIds` when `isGlobal` is false), an alert is of a type that cannot be linked, or there are more than 50 distinct alert IDs.'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ApiError'
              example:
                type: VALIDATION_ERROR
                code: INVALID_INPUT
                description: 'Alert 3631 has unsupported type 18. Supported types: DeviceDisconnected (1), DeviceReconnected (2), Geofence (5), Ignition (8), LowBattery (9), ExceedMaximumSpeed (10).'
                requestId: 0HNLTALNU4DDT:00000001
                docUrl: https://docs.telemax.com.au/errors/invalid-input
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/InternalServerError'
  /v2/api/webhooks/{id}:
    get:
      summary: Get webhook (V2)
      description: Returns a single webhook by ID.
      parameters:
      - name: id
        in: path
        required: true
        schema:
          type: integer
        example: 7
      responses:
        '200':
          description: Successful response
          content:
            application/json:
              example: *id007
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          description: '**404 Not Found** — The requested webhook does not exist or is not accessible.'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ApiError'
              example:
                type: RESOURCE
                code: NOT_FOUND
                description: Webhook was not found.
                requestId: 0HNLTALNU4DE1:00000001
                docUrl: https://docs.telemax.com.au/errors/not-found
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/InternalServerError'
    put:
      summary: Update webhook (V2)
      description: 'Replaces the full configuration of an existing webhook of the token''s companies. Omitted fields take their defaults: `isActive` true, which re-enables a disabled webhook, and `isGlobal` false. Switching to global removes the existing links. See [Supported alert types](/v2/webhooks-overview#supported-alert-types) for the types that can be linked.'
      parameters:
      - name: id
        in: path
        required: true
        schema:
          type: integer
        example: 7
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
              - url
              properties:
                url:
                  type: string
                  format: uri
                  description: '`http` or `https`. The host must contain a dot or be an IP address, must resolve, and must not resolve to a loopback, private, CGNAT, link-local or multicast address, to `0.0.0.0/8` or `240.0.0.0/4`, to `::`, or to an IPv6 unique-local, 6to4 or Teredo address; otherwise the response is 422. A value that cannot be parsed as a URL is not always refused: it can be accepted, and nothing is ever delivered to it.'
                alertIds:
                  type: array
                  items:
                    type: integer
                  description: Alert configuration IDs to link, at most 50 distinct; they replace the current links. Required when `isGlobal` is false; ignored when it is true. Each must be of a type that can be linked (see the endpoint description).
                isActive:
                  type: boolean
                  default: true
                isGlobal:
                  type: boolean
                  default: false
      responses:
        '200':
          description: Successful response. `warning` is set when the linked alerts have different types, or when a webhook that had links switches to global and its links are removed.
          content:
            application/json:
              example: *id007
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          description: '**404 Not Found** — The webhook, or an alert ID, does not exist or is not accessible.'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ApiError'
              example:
                type: RESOURCE
                code: NOT_FOUND
                description: Webhook was not found.
                requestId: 0HNLTALNU4DE2:00000003
                docUrl: https://docs.telemax.com.au/errors/not-found
        '422':
          description: '**422 Unprocessable Entity** — The request failed validation (a missing `url` or one the `url` rules refuse, or empty `alertIds` when `isGlobal` is false), an alert is of a type that cannot be linked, or there are more than 50 distinct alert IDs.'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ApiError'
              examples:
                emptyAlertIds:
                  summary: alertIds required when not global
                  value:
                    type: VALIDATION_ERROR
                    code: INVALID_INPUT
                    description: One or more route or query parameters could not be parsed.
                    requestId: 0HNLTALNU4DE2:00000001
                    docUrl: https://docs.telemax.com.au/errors/invalid-input
                tooManyAlerts:
                  summary: Too many alert IDs
                  value:
                    type: VALIDATION_ERROR
                    code: INVALID_INPUT
                    description: A webhook cannot be linked to more than 50 alerts.
                    requestId: 0HNLTALNU4DE2:00000004
                    docUrl: https://docs.telemax.com.au/errors/invalid-input
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/InternalServerError'
    delete:
      summary: Delete webhook (V2)
      description: Soft-deletes a webhook. Deliveries stop within about 30 seconds; deliveries already queued, including retries, still go out.
      parameters:
      - name: id
        in: path
        required: true
        schema:
          type: integer
        example: 7
      responses:
        '200':
          description: '**200 OK** — Empty body. Webhook deleted successfully.'
          content:
            application/json:
              example: {}
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          description: '**404 Not Found** — The requested webhook does not exist or is not accessible.'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ApiError'
              example:
                type: RESOURCE
                code: NOT_FOUND
                description: Webhook was not found.
                requestId: 0HNLTALNU4DE4:00000002
                docUrl: https://docs.telemax.com.au/errors/not-found
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/InternalServerError'
  /v2/api/webhooks/{id}/link-alert:
    post:
      summary: Link alert to webhook (V2)
      description: Adds an alert configuration to an existing webhook's subscription list. A global webhook cannot have links (422).
      parameters:
      - name: id
        in: path
        required: true
        schema:
          type: integer
        example: 7
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
              - alertId
              properties:
                alertId:
                  type: integer
                  example: 103
      responses:
        '200':
          description: '**200 OK** — Empty body. Alert linked successfully.'
          content:
            application/json:
              example: {}
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          description: '**404 Not Found** — The webhook or alert does not exist or is not accessible.'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ApiError'
              examples:
                webhookNotFound:
                  summary: Webhook not found
                  value:
                    type: RESOURCE
                    code: NOT_FOUND
                    description: Webhook was not found.
                    requestId: 0HNLTALNU4DE6:00000003
                    docUrl: https://docs.telemax.com.au/errors/not-found
                alertNotFound:
                  summary: Alert not found
                  value:
                    type: RESOURCE
                    code: NOT_FOUND
                    description: Alert 99999 was not found.
                    requestId: 0HNLTALNU4DE6:00000004
                    docUrl: https://docs.telemax.com.au/errors/not-found
        '422':
          description: '**422 Unprocessable Entity** — The webhook is global, the alert is of a type that cannot be linked, or the webhook already has 50 linked alerts.'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ApiError'
              examples:
                tooManyAlerts:
                  summary: Too many linked alerts
                  value:
                    type: VALIDATION_ERROR
                    code: INVALID_INPUT
                    description: A webhook cannot be linked to more than 50 alerts.
                    requestId: 0HNLTALNU4DCA:00000003
                    docUrl: https://docs.telemax.com.au/errors/invalid-input
                globalWebhook:
                  summary: Cannot link alert to a global webhook
                  value:
                    type: VALIDATION_ERROR
                    code: INVALID_INPUT
                    description: Cannot link specific alerts to a global webhook.
                    requestId: 0HNLTALNU4DCM:00000001
                    docUrl: https://docs.telemax.com.au/errors/invalid-input
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/InternalServerError'
