{"openapi":"3.1.0","info":{"title":"Traxelio API","version":"2026-07-10","description":"Traxelio is a fleet GPS tracking platform: live vehicle location, theft and fuel-theft protection, driver behavior monitoring, and predictive maintenance for owner-operators and multi-vehicle fleets. This contract covers the authenticated fleet-operation surface (devices, positions, geofences, maintenance, subscriptions) plus the public catalog, checkout, and support\/assistant surfaces used before and around a purchase. Pricing and payments are multi-currency, including mobile money (Wave, Orange Money) alongside cards and wire transfer. To obtain the Bearer token every \"sanctum\"-secured operation requires, call one of the Authentication endpoints (POST \/api\/login, \/api\/login\/email + \/api\/login\/email\/verify, \/api\/login\/google, \/api\/login\/apple, or \/api\/login\/demo) \u2014 there is no other documented way to mint one. The top-level \"x-assistant-features\" extension maps each product capability to the operation_ids that implement it, so an agent integration can go straight from \"what can I do\" to the specific documented operations rather than inferring intent from tags or summaries.","contact":{"name":"Traxelio Support","url":"https:\/\/traxelio.com\/contact","email":"support@traxelio.com"},"termsOfService":"https:\/\/traxelio.com\/terms"},"externalDocs":{"description":"Traxelio product & help documentation","url":"https:\/\/traxelio.com"},"servers":[{"url":"https:\/\/traxelio.com","description":"Production"}],"paths":{"\/api\/login":{"post":{"operationId":"auth.login.store","summary":"Sign in with a password and receive a Bearer token","description":"Repeated failed attempts for the same login+IP pair lock out and return 429 (Laravel's rate limiter, not a plan quota) after config(\"auth.max_attempts\") tries.","tags":["Authentication"],"security":[],"parameters":[],"requestBody":{"required":true,"content":{"application\/json":{"schema":{"type":"object","properties":{"login":{"type":"string","description":"Email or phone number."},"password":{"type":"string"}},"required":["login","password"],"additionalProperties":false}}}},"responses":{"200":{"description":"Signed in.","content":{"application\/json":{"schema":{"type":"object","properties":{"data":{"$ref":"#\/components\/schemas\/User"},"token":{"type":"string","description":"Sanctum plain-text personal access token. Returned exactly once \u2014 only its hash is stored, so a client that loses it must sign in again. Send it as \"Authorization: Bearer <token>\" on every request secured with the sanctum scheme."},"expires_at":{"type":"string","format":"date-time","description":"Token expiry \u2014 30 days."}},"required":["data","token"],"additionalProperties":false}}}},"422":{"$ref":"#\/components\/responses\/ValidationError"},"429":{"$ref":"#\/components\/responses\/TooManyRequests"}}}},"\/api\/login\/email":{"post":{"operationId":"auth.login-email.store","summary":"Request a magic sign-in code by email, or start a password login","description":"The code is valid for 10 minutes; confirm it with POST \/api\/login\/email\/verify, which returns the actual Bearer token. Resends within a 60-second cooldown reuse the previously cached code instead of generating a new one. No account is created at this step \u2014 a new user only comes into existence on a successful POST \/api\/login\/email\/verify.","tags":["Authentication"],"security":[],"parameters":[],"requestBody":{"required":true,"content":{"application\/json":{"schema":{"type":"object","properties":{"email":{"type":"string","format":"email"},"use_password":{"type":["boolean","null"],"description":"When true and the account already has a password set, this call is a no-op (no code is sent) \u2014 the client is expected to collect the password and call POST \/api\/login instead. Default\/absent behaves as false: a 6-digit code is emailed regardless of whether the account has a password."}},"required":["email"],"additionalProperties":false}}}},"responses":{"200":{"description":"Code sent (or reused within the cooldown window). temp_id is an opaque correlation id echoed back to the client for its own bookkeeping; verification only needs email + code, not temp_id.","content":{"application\/json":{"schema":{"type":"object","properties":{"temp_id":{"type":["string","null"],"description":"Null when the account already has a password and use_password was true (no code was sent)."}},"required":["temp_id"],"additionalProperties":false}}}},"429":{"description":"More than 5 code requests for this email in the last hour.","content":{"application\/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"],"additionalProperties":false}}}},"500":{"description":"The verification email failed to send.","content":{"application\/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"],"additionalProperties":false}}}},"422":{"$ref":"#\/components\/responses\/ValidationError"}}}},"\/api\/login\/email\/verify":{"post":{"operationId":"auth.login-email-verify.store","summary":"Confirm the emailed code and receive a Bearer token","description":"Creates the account on first-ever successful verification for that email (no account exists yet from POST \/api\/login\/email alone). A wrong code is a standard 422 field error on \"login\", not a dedicated error shape; the code locks after 5 wrong attempts until a new one is requested.","tags":["Authentication"],"security":[],"parameters":[],"requestBody":{"required":true,"content":{"application\/json":{"schema":{"type":"object","properties":{"email":{"type":"string"},"code":{"type":"string","description":"6-digit code from POST \/api\/login\/email."}},"required":["email","code"],"additionalProperties":false}}}},"responses":{"200":{"description":"Signed in (or newly registered).","content":{"application\/json":{"schema":{"type":"object","properties":{"data":{"$ref":"#\/components\/schemas\/User"},"token":{"type":"string","description":"Sanctum plain-text personal access token. Returned exactly once \u2014 only its hash is stored, so a client that loses it must sign in again. Send it as \"Authorization: Bearer <token>\" on every request secured with the sanctum scheme."},"expires_at":{"type":"string","format":"date-time","description":"Token expiry \u2014 30 days."}},"required":["data","token"],"additionalProperties":false}}}},"422":{"$ref":"#\/components\/responses\/ValidationError"}}}},"\/api\/login\/google":{"post":{"operationId":"auth.login-google.store","summary":"Exchange a Google ID token for a Bearer token (mobile app flow)","description":"First-time sign-in creates the account from the token's email\/name\/picture claims. A 422 field error on \"google\" covers every failure mode (invalid audience\/issuer, expired token, provider error) \u2014 the message does not distinguish which.","tags":["Authentication"],"security":[],"parameters":[],"requestBody":{"required":true,"content":{"application\/json":{"schema":{"type":"object","properties":{"id_token":{"type":"string","description":"Google Sign-In ID token (JWT) from the native SDK."}},"required":["id_token"],"additionalProperties":false}}}},"responses":{"200":{"description":"Signed in (or newly registered).","content":{"application\/json":{"schema":{"type":"object","properties":{"data":{"$ref":"#\/components\/schemas\/User"},"token":{"type":"string","description":"Sanctum plain-text personal access token. Returned exactly once \u2014 only its hash is stored, so a client that loses it must sign in again. Send it as \"Authorization: Bearer <token>\" on every request secured with the sanctum scheme."},"expires_at":{"type":"string","format":"date-time","description":"Token expiry \u2014 30 days."}},"required":["data","token"],"additionalProperties":false}}}},"422":{"$ref":"#\/components\/responses\/ValidationError"}}}},"\/api\/login\/apple":{"post":{"operationId":"auth.login-apple.store","summary":"Exchange an Apple ID token for a Bearer token (mobile app flow)","description":"First-time sign-in creates the account from the token's email claim and, when present, the \"user\" name payload.","tags":["Authentication"],"security":[],"parameters":[],"requestBody":{"required":true,"content":{"application\/json":{"schema":{"type":"object","properties":{"id_token":{"type":"string","description":"Apple Sign In identity token (JWT) from the native SDK."},"user":{"type":["object","null"],"description":"Apple only sends the user's name on the very first authorization; forward it verbatim here on that first call ({name: {firstName, lastName}}), omit on subsequent ones.","additionalProperties":true}},"required":["id_token"],"additionalProperties":false}}}},"responses":{"200":{"description":"Signed in (or newly registered).","content":{"application\/json":{"schema":{"type":"object","properties":{"data":{"$ref":"#\/components\/schemas\/User"},"token":{"type":"string","description":"Sanctum plain-text personal access token. Returned exactly once \u2014 only its hash is stored, so a client that loses it must sign in again. Send it as \"Authorization: Bearer <token>\" on every request secured with the sanctum scheme."},"expires_at":{"type":"string","format":"date-time","description":"Token expiry \u2014 30 days."}},"required":["data","token"],"additionalProperties":false}}}},"422":{"$ref":"#\/components\/responses\/ValidationError"}}}},"\/api\/subscription\/plan":{"get":{"operationId":"discover.subscription-plan.index","summary":"List subscription plans\/tiers with pricing across all durations","tags":["Discover"],"security":[],"parameters":[],"responses":{"200":{"description":"List subscription plans\/tiers with pricing across all durations","content":{"application\/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"type":"object","properties":{"label":{"type":"string"},"value":{"type":"string"},"description":{"type":"string"},"price":{"type":"integer"},"price_text":{"type":"string"},"features":{"type":"array","items":{"type":"string"}},"icon":{"type":"string"},"default":{"type":"boolean"},"prices":{"type":"object","additionalProperties":{"type":"object","properties":{"price":{"type":"integer"},"price_text":{"type":"string"}},"additionalProperties":false}}},"additionalProperties":false}}},"additionalProperties":false}}}}}}},"\/api\/plan-duration":{"get":{"operationId":"discover.plan-duration.index","summary":"List plan duration\/billing period options (monthly, annual, lifetime, \u2026)","tags":["Discover"],"security":[],"parameters":[{"name":"subscription_id","in":"query","required":false,"description":"Existing subscription to price durations for. Omit for generic option labels.","schema":{"type":"string"}}],"responses":{"200":{"description":"List plan duration\/billing period options (monthly, annual, lifetime, \u2026)","content":{"application\/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"type":"object","properties":{"value":{"type":"string"},"label":{"type":"string"},"saving_percentage":{"type":["integer","null"]}},"additionalProperties":false}}},"additionalProperties":false}}}}}}},"\/api\/billing-period":{"get":{"operationId":"discover.billing-period.index","summary":"Legacy alias for \/plan-duration","tags":["Discover"],"security":[],"parameters":[{"name":"subscription_id","in":"query","required":false,"description":"Existing subscription to price durations for. Omit for generic option labels.","schema":{"type":"string"}}],"responses":{"200":{"description":"Legacy alias for \/plan-duration","content":{"application\/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"type":"object","properties":{"value":{"type":"string"},"label":{"type":"string"},"saving_percentage":{"type":["integer","null"]}},"additionalProperties":false}}},"additionalProperties":false}}}}}}},"\/api\/payment\/method":{"get":{"operationId":"discover.payment-method.index","summary":"List available payment methods for a currency","tags":["Discover"],"security":[],"parameters":[{"name":"currency","in":"query","required":false,"description":"ISO currency code (e.g. XOF, USD). Defaults to the app's default currency.","schema":{"type":"string"}}],"responses":{"200":{"description":"List available payment methods for a currency","content":{"application\/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"type":"object","properties":{"value":{"type":"string"},"label":{"type":"string"}},"additionalProperties":false}}},"additionalProperties":false}}}}}}},"\/api\/insurance\/price":{"get":{"operationId":"discover.insurance-price.index","summary":"Quote an insurance price for a device category and coverage set","tags":["Discover"],"security":[],"parameters":[{"name":"category","in":"query","required":false,"description":"Device category enum value.","schema":{"type":"string"}},{"name":"horsepower","in":"query","required":false,"description":"Vehicle horsepower.","schema":{"type":"integer"}},{"name":"engine_displacement","in":"query","required":false,"description":"Engine displacement.","schema":{"type":"integer"}},{"name":"duration","in":"query","required":false,"description":"Coverage duration.","schema":{"type":"integer"}},{"name":"coverages","in":"query","required":false,"description":"Coverage type enum values.","schema":{"type":"array","items":{"type":"string"}}}],"responses":{"200":{"description":"Quote an insurance price for a device category and coverage set","content":{"application\/json":{"schema":{"type":"object","properties":{"data":{"type":"object","properties":{"price":{"type":["integer","null"]}},"additionalProperties":false}},"additionalProperties":false}}}}}}},"\/api\/insurance\/coverage":{"get":{"operationId":"discover.insurance-coverage.index","summary":"List insurance coverage types","tags":["Discover"],"security":[],"parameters":[],"responses":{"200":{"description":"List insurance coverage types","content":{"application\/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"type":"object","properties":{"value":{"type":"string"},"label":{"type":"string"},"selected":{"type":"boolean"},"disabled":{"type":"boolean"}},"additionalProperties":false}}},"additionalProperties":false}}}}}}},"\/api\/countries":{"get":{"operationId":"discover.countries.index","summary":"List countries the platform operates in","tags":["Discover"],"security":[],"parameters":[],"responses":{"200":{"description":"List countries the platform operates in","content":{"application\/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"type":"object","properties":{"value":{"type":"string"},"label":{"type":"string"}},"additionalProperties":false}}},"additionalProperties":false}}}}}}},"\/api\/device-categories":{"get":{"operationId":"discover.device-categories.index","summary":"List device\/vehicle categories","tags":["Discover"],"security":[],"parameters":[],"responses":{"200":{"description":"List device\/vehicle categories","content":{"application\/json":{"schema":{"type":"array","items":{"type":"object","properties":{"value":{"type":"string"},"label":{"type":"string"},"has_landing_page":{"type":"boolean"},"featured":{"type":"boolean"}},"additionalProperties":false}}}}}}}},"\/api\/store":{"get":{"operationId":"discover.store.index","summary":"List published stores, closest-country-first (capped at 100)","tags":["Discover"],"security":[],"parameters":[{"name":"country","in":"query","required":false,"description":"ISO country code to rank\/suggest by. Overrides the request's GeoIP-resolved country \u2014 use this to browse a specific market from anywhere.","schema":{"type":"string"}}],"responses":{"200":{"description":"List published stores, closest-country-first (capped at 100)","content":{"application\/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"address":{"type":["string","null"]},"country":{"type":"string"},"products_count":{"type":"integer"}},"additionalProperties":false}},"suggested_store_id":{"type":["string","null"]}},"additionalProperties":false}}}}}}},"\/api\/store\/{store}\/product":{"get":{"operationId":"discover.store-product.index","summary":"List products carried by a store","tags":["Discover"],"security":[],"parameters":[{"name":"store","in":"path","required":true,"description":"Store ID.","schema":{"type":"string"}},{"name":"sort","in":"query","required":false,"description":"most_sold (default), price_desc, price_asc, or name_asc.","schema":{"type":"string"}},{"name":"search","in":"query","required":false,"description":"Filter by product name.","schema":{"type":"string"}},{"name":"categories","in":"query","required":false,"description":"Comma-separated device category values.","schema":{"type":"string"}},{"name":"page","in":"query","required":false,"description":"Page number (1-indexed).","schema":{"type":"integer"}},{"name":"per_page","in":"query","required":false,"description":"Page size.","schema":{"type":"integer"}}],"responses":{"200":{"description":"List products carried by a store","content":{"application\/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#\/components\/schemas\/Product"}},"links":{"$ref":"#\/components\/schemas\/PaginationLinks"},"meta":{"$ref":"#\/components\/schemas\/PaginationMeta"}},"additionalProperties":false}}}},"403":{"$ref":"#\/components\/responses\/Forbidden"},"404":{"$ref":"#\/components\/responses\/NotFound"}}}},"\/api\/product":{"get":{"operationId":"discover.product.index","summary":"List published products in an explicit currency\/country, or the request's GeoIP-resolved currency when omitted","description":"meta.currency always reports which currency the returned page was priced in \u2014 read it rather than assuming the requested\/GeoIP currency, since an empty-result retry can silently switch to the app default.","tags":["Discover"],"security":[],"parameters":[{"name":"currency","in":"query","required":false,"description":"ISO currency code (e.g. XOF, USD). Takes priority over country and GeoIP. Recommended: omitting both currency and country makes the result GeoIP-dependent (varies by caller IP) and \u2014 for a market with no matching products \u2014 automatically retries once against the app's default currency, echoed back in meta.currency.","schema":{"type":"string"}},{"name":"country","in":"query","required":false,"description":"ISO country code; resolved to that country's currency when currency is omitted.","schema":{"type":"string"}},{"name":"gps","in":"query","required":false,"description":"Filter to devices with (true) or without (false) built-in GPS.","schema":{"type":"boolean"}},{"name":"categories","in":"query","required":false,"description":"Comma-separated device category values.","schema":{"type":"string"}},{"name":"per_page","in":"query","required":false,"description":"Page size, 1-30 (default 15).","schema":{"type":"integer"}},{"name":"page","in":"query","required":false,"description":"Page number (1-indexed).","schema":{"type":"integer"}}],"responses":{"200":{"description":"List published products in an explicit currency\/country, or the request's GeoIP-resolved currency when omitted","content":{"application\/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#\/components\/schemas\/Product"}},"links":{"$ref":"#\/components\/schemas\/PaginationLinks"},"meta":{"$ref":"#\/components\/schemas\/PaginationMeta"}},"additionalProperties":false},"examples":{"default":{"$ref":"#\/components\/examples\/ProductListResponse"}}}}}}}},"\/api\/product\/{product}":{"get":{"operationId":"discover.product.show","summary":"Get product detail","tags":["Discover"],"security":[],"parameters":[{"name":"product","in":"path","required":true,"description":"Product ID.","schema":{"type":"string"}}],"responses":{"200":{"description":"Get product detail","content":{"application\/json":{"schema":{"type":"object","properties":{"data":{"$ref":"#\/components\/schemas\/Product"}},"additionalProperties":false}}}},"403":{"$ref":"#\/components\/responses\/Forbidden"},"404":{"$ref":"#\/components\/responses\/NotFound"}}}},"\/api\/product\/{product}\/addon":{"get":{"operationId":"discover.product-addon.index","summary":"List add-ons available for a product","tags":["Discover"],"security":[],"parameters":[{"name":"product","in":"path","required":true,"description":"Product ID.","schema":{"type":"string"}},{"name":"page","in":"query","required":false,"description":"Page number (1-indexed).","schema":{"type":"integer"}},{"name":"per_page","in":"query","required":false,"description":"Page size.","schema":{"type":"integer"}}],"responses":{"200":{"description":"List add-ons available for a product","content":{"application\/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#\/components\/schemas\/Product"}},"links":{"$ref":"#\/components\/schemas\/PaginationLinks"},"meta":{"$ref":"#\/components\/schemas\/PaginationMeta"}},"additionalProperties":false}}}},"403":{"$ref":"#\/components\/responses\/Forbidden"},"404":{"$ref":"#\/components\/responses\/NotFound"}}}},"\/api\/geozone-template":{"get":{"operationId":"discover.geozone-template.index","summary":"List published geozone templates (pre-drawn zones for sale) \u2014 capped at 100\/page","description":"Guests always get metadata only. Authenticated callers (Bearer token) additionally get polygon geometry when with_polygon=1 is passed.","tags":["Discover"],"security":[],"parameters":[{"name":"country_code","in":"query","required":false,"description":"ISO country code.","schema":{"type":"string"}},{"name":"type","in":"query","required":false,"description":"Geozone type.","schema":{"type":"string","enum":["country","region","city"]}},{"name":"search","in":"query","required":false,"description":"Filter by name.","schema":{"type":"string"}},{"name":"with_polygon","in":"query","required":false,"description":"Include polygon geometry in each item. Ignored for guests, who always get metadata only regardless of this flag \u2014 honored only with a valid Bearer token.","schema":{"type":"boolean"}},{"name":"per_page","in":"query","required":false,"description":"Page size, 1-100 (default 50).","schema":{"type":"integer"}},{"name":"page","in":"query","required":false,"description":"Page number (1-indexed).","schema":{"type":"integer"}}],"responses":{"200":{"description":"List published geozone templates (pre-drawn zones for sale) \u2014 capped at 100\/page","content":{"application\/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#\/components\/schemas\/GeozoneTemplate"}},"links":{"$ref":"#\/components\/schemas\/PaginationLinks"},"meta":{"$ref":"#\/components\/schemas\/PaginationMeta"}},"additionalProperties":false}}}}}}},"\/api\/geozone-template\/countries":{"get":{"operationId":"discover.geozone-template-countries.index","summary":"List countries with published geozone templates, grouped with region\/city counts","tags":["Discover"],"security":[],"parameters":[],"responses":{"200":{"description":"List countries with published geozone templates, grouped with region\/city counts","content":{"application\/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"type":"object","properties":{"country_code":{"type":"string"},"name":{"type":"string"},"slug":{"type":"string"},"continent":{"type":["string","null"]},"center_lat":{"type":"number"},"center_lng":{"type":"number"},"counts":{"type":"object","properties":{"region":{"type":"integer"},"city":{"type":"integer"}},"additionalProperties":false}},"additionalProperties":false}}},"additionalProperties":false}}}}}}},"\/api\/geozone-template\/{template}":{"get":{"operationId":"discover.geozone-template.show","summary":"Get a geozone template","description":"Guests get metadata only. Authenticated callers (Bearer token) additionally receive a polygon field with the zone's geometry \u2014 no with_polygon flag needed here, unlike index.","tags":["Discover"],"security":[],"parameters":[{"name":"template","in":"path","required":true,"description":"Geozone template slug (GeozoneTemplate's route key is slug, not id).","schema":{"type":"string"}}],"responses":{"200":{"description":"Get a geozone template","content":{"application\/json":{"schema":{"type":"object","properties":{"data":{"$ref":"#\/components\/schemas\/GeozoneTemplate"}},"additionalProperties":false}}}},"403":{"$ref":"#\/components\/responses\/Forbidden"},"404":{"$ref":"#\/components\/responses\/NotFound"}}}},"\/api\/technician":{"get":{"operationId":"discover.technician.index","summary":"List technicians; contact fields unlock only after that technician confirms an appointment (see field description)","tags":["Discover"],"security":[],"parameters":[{"name":"q","in":"query","required":false,"description":"Search by name. Authenticated requesters also match on email\/phone; guests match on name only.","schema":{"type":"string"}},{"name":"page","in":"query","required":false,"description":"Page number (1-indexed).","schema":{"type":"integer"}},{"name":"per_page","in":"query","required":false,"description":"Page size.","schema":{"type":"integer"}}],"responses":{"200":{"description":"List technicians; contact fields unlock only after that technician confirms an appointment (see field description)","content":{"application\/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"type":"object","properties":{"ulid":{"type":"string"},"name":{"type":"string"},"avatar_url":{"type":["string","null"]},"phone":{"type":["string","null"],"description":"Present only once THIS technician has confirmed an appointment with the requester (technician-side confirm flow). Absent for guests and for authenticated users without a technician-confirmed appointment with this technician."},"email":{"type":["string","null"],"description":"Present only once THIS technician has confirmed an appointment with the requester (technician-side confirm flow). Absent for guests and for authenticated users without a technician-confirmed appointment with this technician."}},"additionalProperties":false}},"links":{"$ref":"#\/components\/schemas\/PaginationLinks"},"meta":{"$ref":"#\/components\/schemas\/PaginationMeta"}},"additionalProperties":false}}}}}}},"\/api\/technician\/{technician}\/availability":{"get":{"operationId":"discover.technician-availability.index","summary":"List a technician's open\/booked time slots for a date \u2014 no PII","tags":["Discover"],"security":[],"parameters":[{"name":"technician","in":"path","required":true,"description":"Technician user ULID.","schema":{"type":"string"}},{"name":"date","in":"query","required":true,"description":"Date to check, today or later.","schema":{"type":"string","format":"date"}}],"responses":{"200":{"description":"List a technician's open\/booked time slots for a date \u2014 no PII","content":{"application\/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"type":"object","properties":{"time":{"type":"string"},"available":{"type":"boolean"}},"additionalProperties":false}}},"additionalProperties":false}}}},"403":{"$ref":"#\/components\/responses\/Forbidden"},"404":{"$ref":"#\/components\/responses\/NotFound"}}}},"\/api\/place":{"get":{"operationId":"discover.place.index","summary":"Address search \/ geocoding for delivery & installation planning","description":"Rate limited per IP. Guests share a low keyless cap (10\/min, 50\/hr, 500\/day total) so anonymous scraping can't exhaust the quota; authenticated callers get higher per-user limits (30\/min, 200\/hr, 500\/day) plus a keyless 5,000\/day global cap that bounds shared Google Places spend.","tags":["Discover"],"security":[],"parameters":[{"name":"q","in":"query","required":true,"description":"Address or place query, minimum 3 characters.","schema":{"type":"string"}}],"responses":{"200":{"description":"Address search \/ geocoding for delivery & installation planning","content":{"application\/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"type":"object","properties":{"address":{"type":"string"},"description":{"type":["string","null"]},"place_id":{"type":["string","null"]}},"additionalProperties":false}}},"additionalProperties":false}}}},"429":{"$ref":"#\/components\/responses\/TooManyRequests"}}}},"\/api\/place\/{place}":{"get":{"operationId":"discover.place.show","summary":"Resolve a place ID or \"lat,lng\" pair to a full address","description":"Rate limited per IP. Guests share a low keyless cap (10\/min, 50\/hr, 500\/day total) so anonymous scraping can't exhaust the quota; authenticated callers get higher per-user limits (30\/min, 200\/hr, 500\/day) plus a keyless 5,000\/day global cap that bounds shared Google Places spend.","tags":["Discover"],"security":[],"parameters":[{"name":"place","in":"path","required":true,"description":"Google place ID, or a \"lat,lng\" pair.","schema":{"type":"string"}}],"responses":{"200":{"description":"Resolve a place ID or \"lat,lng\" pair to a full address","content":{"application\/json":{"schema":{"type":"object","properties":{"data":{"type":"object","properties":{"address":{"type":["string","null"]},"latitude":{"type":["number","null"]},"longitude":{"type":["number","null"]},"street":{"type":["string","null"]},"city":{"type":["string","null"]},"state":{"type":["string","null"]},"zip":{"type":["string","null"]},"country":{"type":["string","null"]}},"additionalProperties":false}},"additionalProperties":false}}}},"403":{"$ref":"#\/components\/responses\/Forbidden"},"404":{"$ref":"#\/components\/responses\/NotFound"},"429":{"$ref":"#\/components\/responses\/TooManyRequests"}}}},"\/api\/cart":{"get":{"operationId":"convert.cart.show","summary":"Get the current user's cart (created automatically on first read)","tags":["Convert"],"security":[{"sanctum":[]},{"oauth2":["cart.update"]}],"parameters":[],"responses":{"200":{"description":"Cart contents.","content":{"application\/json":{"schema":{"type":"object","properties":{"data":{"$ref":"#\/components\/schemas\/Cart"}},"additionalProperties":false}}}},"401":{"$ref":"#\/components\/responses\/Unauthorized"},"403":{"$ref":"#\/components\/responses\/Forbidden"}}},"delete":{"operationId":"convert.cart.destroy","summary":"Clear all items from the cart","tags":["Convert"],"security":[{"sanctum":[]},{"oauth2":["cart.update"]}],"parameters":[],"responses":{"200":{"description":"Emptied cart.","content":{"application\/json":{"schema":{"type":"object","properties":{"data":{"$ref":"#\/components\/schemas\/Cart"}},"additionalProperties":false}}}},"401":{"$ref":"#\/components\/responses\/Unauthorized"},"403":{"$ref":"#\/components\/responses\/Forbidden"}}}},"\/api\/cart\/item":{"post":{"operationId":"convert.cart-item.store","summary":"Add a product (and optional add-ons) to the cart","description":"Replaces any existing product line item \u2014 this cart model holds a single primary product bundle at a time.","tags":["Convert"],"security":[{"sanctum":[]},{"oauth2":["cart.update"]}],"parameters":[],"requestBody":{"required":true,"content":{"application\/json":{"schema":{"type":"object","properties":{"product_id":{"type":"string","description":"Product ULID. Must be sent as a JSON string \u2014 a bare JSON integer 422s."},"addons":{"type":"array","items":{"type":"string","description":"Add-on product ULID."}},"device_ulids":{"type":"array","items":{"type":["string","null"],"description":"Device ULID to associate with this line item."}}},"required":["product_id"],"additionalProperties":false},"examples":{"default":{"$ref":"#\/components\/examples\/CartItemStoreRequest"}}}}},"responses":{"200":{"description":"Updated cart.","content":{"application\/json":{"schema":{"type":"object","properties":{"data":{"$ref":"#\/components\/schemas\/Cart"}},"additionalProperties":false},"examples":{"default":{"$ref":"#\/components\/examples\/CartResponse"}}}}},"401":{"$ref":"#\/components\/responses\/Unauthorized"},"403":{"$ref":"#\/components\/responses\/Forbidden"},"422":{"$ref":"#\/components\/responses\/ValidationError"}}}},"\/api\/cart\/item\/{cartItem}":{"patch":{"operationId":"convert.cart-item.update","summary":"Update a cart item's quantity","tags":["Convert"],"security":[{"sanctum":[]},{"oauth2":["cart.update"]}],"parameters":[{"name":"cartItem","in":"path","required":true,"description":"Cart item ID.","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application\/json":{"schema":{"type":"object","properties":{"quantity":{"type":"integer","description":"Minimum 1."}},"required":["quantity"],"additionalProperties":false}}}},"responses":{"200":{"description":"Updated cart.","content":{"application\/json":{"schema":{"type":"object","properties":{"data":{"$ref":"#\/components\/schemas\/Cart"}},"additionalProperties":false}}}},"401":{"$ref":"#\/components\/responses\/Unauthorized"},"403":{"$ref":"#\/components\/responses\/Forbidden"},"404":{"$ref":"#\/components\/responses\/NotFound"},"422":{"$ref":"#\/components\/responses\/ValidationError"}}},"delete":{"operationId":"convert.cart-item.destroy","summary":"Remove a cart item \u2014 bundled add-ons sharing its bundle_id are removed too","tags":["Convert"],"security":[{"sanctum":[]},{"oauth2":["cart.update"]}],"parameters":[{"name":"cartItem","in":"path","required":true,"description":"Cart item ID.","schema":{"type":"string"}}],"responses":{"200":{"description":"Updated cart.","content":{"application\/json":{"schema":{"type":"object","properties":{"data":{"$ref":"#\/components\/schemas\/Cart"}},"additionalProperties":false}}}},"401":{"$ref":"#\/components\/responses\/Unauthorized"},"403":{"$ref":"#\/components\/responses\/Forbidden"},"404":{"$ref":"#\/components\/responses\/NotFound"}}}},"\/api\/order":{"post":{"operationId":"convert.order.store","summary":"Create an order and pending payment from the current cart, returning a gateway checkout URL","description":"Requires a non-empty cart with a purchasable product line item. Rate limited.","tags":["Convert"],"security":[{"sanctum":[]},{"oauth2":["order.create"]}],"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"description":"Optional client-supplied key (max 255 chars). The first successful (2xx) response for a key is cached 24h and replayed verbatim on retry with the same request body (adds Idempotency-Replayed: true). The same key with a different body 422s; a concurrent request reusing an in-flight key 409s instead of executing twice.","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application\/json":{"schema":{"type":"object","properties":{"device_id":{"type":["string","null"],"description":"Device ULID to attach the order to."},"payment_method":{"type":"string","enum":["stripe","cash","wave","orange_money","wire","paypal"]},"shipping_zone":{"type":["string","null"],"description":"Predefined shipping zone key. When set, shipping_address_line_1\/city\/country become optional."},"shipping_address_line_1":{"type":["string","null"],"description":"Required unless shipping_zone is set."},"shipping_address_line_2":{"type":["string","null"]},"shipping_address_city":{"type":["string","null"],"description":"Required unless shipping_zone is set."},"shipping_address_state":{"type":["string","null"],"description":"Required when shipping_address_country is US or CA."},"shipping_address_zip":{"type":["string","null"],"description":"Required when shipping_address_country is US or CA."},"shipping_address_country":{"type":["string","null"],"description":"ISO country code. Required unless shipping_zone is set."},"shipping_address_latitude":{"type":["number","null"]},"shipping_address_longitude":{"type":["number","null"]},"billing_same_as_shipping":{"type":["string","null"],"description":"\"1\" (default) reuses the shipping address for billing; \"0\" requires the billing_address_* fields."},"billing_address_line_1":{"type":["string","null"],"description":"Required when billing_same_as_shipping is \"0\"."},"billing_address_line_2":{"type":["string","null"]},"billing_address_city":{"type":["string","null"],"description":"Required when billing_same_as_shipping is \"0\"."},"billing_address_state":{"type":["string","null"]},"billing_address_zip":{"type":["string","null"]},"billing_address_country":{"type":["string","null"],"description":"ISO country code. Required when billing_same_as_shipping is \"0\"."},"billing_address_latitude":{"type":["number","null"]},"billing_address_longitude":{"type":["number","null"]},"success_url":{"type":"string","format":"uri","description":"Redirect target after a successful gateway payment."}},"required":["payment_method","success_url"],"additionalProperties":false},"examples":{"default":{"$ref":"#\/components\/examples\/OrderCheckoutRequest"}}}}},"responses":{"200":{"description":"Created order + pending payment.","content":{"application\/json":{"schema":{"type":"object","properties":{"data":{"type":"object","properties":{"order_id":{"type":"string"},"payment_id":{"type":"string"},"checkout_url":{"type":"string","format":"uri","description":"Payment gateway URL the human completes to pay."}},"additionalProperties":false}},"additionalProperties":false},"examples":{"default":{"$ref":"#\/components\/examples\/OrderCheckoutResponse"}}}}},"401":{"$ref":"#\/components\/responses\/Unauthorized"},"403":{"$ref":"#\/components\/responses\/Forbidden"},"422":{"$ref":"#\/components\/responses\/ValidationError"},"429":{"$ref":"#\/components\/responses\/TooManyRequests"}}}},"\/api\/order\/{order}":{"get":{"operationId":"convert.order.show","summary":"Get order detail, including line items and payment history","tags":["Convert"],"security":[{"sanctum":[]},{"oauth2":["order.view"]}],"parameters":[{"name":"order","in":"path","required":true,"description":"Order ID.","schema":{"type":"string"}}],"responses":{"200":{"description":"Order detail.","content":{"application\/json":{"schema":{"type":"object","properties":{"data":{"$ref":"#\/components\/schemas\/Order"}},"additionalProperties":false}}}},"401":{"$ref":"#\/components\/responses\/Unauthorized"},"403":{"$ref":"#\/components\/responses\/Forbidden"},"404":{"$ref":"#\/components\/responses\/NotFound"}}}},"\/api\/order\/{order}\/progress":{"get":{"operationId":"convert.order-progress.index","summary":"Get an order's fulfillment stage timeline (payment, delivery\/appointment, device setup, tracker live)","tags":["Convert"],"security":[{"sanctum":[]},{"oauth2":["order.view"]}],"parameters":[{"name":"order","in":"path","required":true,"description":"Order ID.","schema":{"type":"string"}}],"responses":{"200":{"description":"Ordered timeline stages.","content":{"application\/json":{"schema":{"type":"object","properties":{"stages":{"type":"array","items":{"type":"object","properties":{"key":{"type":"string"},"label":{"type":"string"},"timestamp":{"type":["string","null"],"format":"date-time"},"status":{"type":"string","enum":["pending","current","done"]}},"additionalProperties":false}}},"additionalProperties":false}}}},"401":{"$ref":"#\/components\/responses\/Unauthorized"},"403":{"$ref":"#\/components\/responses\/Forbidden"},"404":{"$ref":"#\/components\/responses\/NotFound"}}}},"\/api\/order\/{order}\/checkout":{"post":{"operationId":"convert.order-checkout.store","summary":"Create a new payment + gateway checkout URL for an unpaid order","description":"422 if the order is already paid.","tags":["Convert"],"security":[{"sanctum":[]},{"oauth2":["order.checkout"]}],"parameters":[{"name":"order","in":"path","required":true,"description":"Order ID.","schema":{"type":"string"}},{"name":"Idempotency-Key","in":"header","required":false,"description":"Optional client-supplied key (max 255 chars). The first successful (2xx) response for a key is cached 24h and replayed verbatim on retry with the same request body (adds Idempotency-Replayed: true). The same key with a different body 422s; a concurrent request reusing an in-flight key 409s instead of executing twice.","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application\/json":{"schema":{"type":"object","properties":{"payment_method":{"type":"string","enum":["stripe","cash","wave","orange_money","wire","paypal"]}},"required":["payment_method"],"additionalProperties":false}}}},"responses":{"200":{"description":"Pending payment + checkout URL.","content":{"application\/json":{"schema":{"type":"object","properties":{"data":{"$ref":"#\/components\/schemas\/CheckoutResult"}},"additionalProperties":false}}}},"401":{"$ref":"#\/components\/responses\/Unauthorized"},"403":{"$ref":"#\/components\/responses\/Forbidden"},"404":{"$ref":"#\/components\/responses\/NotFound"},"422":{"$ref":"#\/components\/responses\/ValidationError"}}}},"\/api\/order\/{order}\/addon":{"post":{"operationId":"convert.order-addon.store","summary":"Add add-ons to an already-paid order, creating a new payment for the incremental amount","description":"The order must already be paid. payment_id\/checkout_url come back null when none of the requested add-ons are valid or new.","tags":["Convert"],"security":[{"sanctum":[]},{"oauth2":["order.create"]}],"parameters":[{"name":"order","in":"path","required":true,"description":"Order ID.","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application\/json":{"schema":{"type":"object","properties":{"addon_ids":{"type":"array","items":{"type":"string","description":"Add-on product ULID."}},"payment_method":{"type":"string","enum":["stripe","cash","wave","orange_money","wire","paypal"]},"success_url":{"type":["string","null"],"format":"uri"}},"required":["addon_ids","payment_method"],"additionalProperties":false}}}},"responses":{"200":{"description":"Order + payment references.","content":{"application\/json":{"schema":{"type":"object","properties":{"data":{"type":"object","properties":{"order_id":{"type":"string"},"payment_id":{"type":["string","null"]},"checkout_url":{"type":["string","null"],"format":"uri"}},"additionalProperties":false}},"additionalProperties":false}}}},"401":{"$ref":"#\/components\/responses\/Unauthorized"},"403":{"$ref":"#\/components\/responses\/Forbidden"},"404":{"$ref":"#\/components\/responses\/NotFound"},"422":{"$ref":"#\/components\/responses\/ValidationError"}}}},"\/api\/geozone-template\/{template}\/purchase":{"post":{"operationId":"convert.geozone-template-purchase.store","summary":"Buy a geozone template for one of the caller's own devices","description":"422 if the caller already owns this geozone, or has hit their plan's geozone limit. Rate limited.","tags":["Convert"],"security":[{"sanctum":[]},{"oauth2":["order.checkout"]}],"parameters":[{"name":"template","in":"path","required":true,"description":"Geozone template slug (route key is slug, not id).","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application\/json":{"schema":{"type":"object","properties":{"device_id":{"type":"string","description":"Device ULID; must belong to the authenticated user."},"payment_method":{"type":"string","enum":["stripe","cash","wave","orange_money","wire","paypal"]}},"required":["device_id","payment_method"],"additionalProperties":false}}}},"responses":{"200":{"description":"Pending payment + checkout URL.","content":{"application\/json":{"schema":{"type":"object","properties":{"data":{"$ref":"#\/components\/schemas\/CheckoutResult"}},"additionalProperties":false}}}},"401":{"$ref":"#\/components\/responses\/Unauthorized"},"403":{"$ref":"#\/components\/responses\/Forbidden"},"404":{"$ref":"#\/components\/responses\/NotFound"},"422":{"$ref":"#\/components\/responses\/ValidationError"},"429":{"$ref":"#\/components\/responses\/TooManyRequests"}}}},"\/api\/device\/{device}\/setup":{"post":{"operationId":"activate.device-setup.store","summary":"Complete GPS device setup: bind phone\/unique ID, enable it on the tracking server, apply the team's default settings template if any","description":"Rate limited to 3 setup attempts per hour per user+device.","tags":["Activate"],"security":[{"sanctum":[]},{"oauth2":["device.activate"]}],"parameters":[{"name":"device","in":"path","required":true,"description":"Device ULID.","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application\/json":{"schema":{"type":"object","properties":{"phone":{"type":"string"},"unique_id":{"type":"string","description":"Tracker hardware\/SIM identifier."}},"required":["phone","unique_id"],"additionalProperties":false},"examples":{"default":{"$ref":"#\/components\/examples\/DeviceSetupRequest"}}}}},"responses":{"200":{"description":"Set-up device.","content":{"application\/json":{"schema":{"type":"object","properties":{"data":{"$ref":"#\/components\/schemas\/Device"}},"additionalProperties":false},"examples":{"default":{"$ref":"#\/components\/examples\/DeviceSetupResponse"}}}}},"401":{"$ref":"#\/components\/responses\/Unauthorized"},"403":{"$ref":"#\/components\/responses\/Forbidden"},"404":{"$ref":"#\/components\/responses\/NotFound"},"422":{"$ref":"#\/components\/responses\/ValidationError"},"429":{"$ref":"#\/components\/responses\/TooManyRequests"}}}},"\/api\/device\/{device}\/order\/plan":{"post":{"operationId":"activate.device-order-plan.store","summary":"Select a subscription plan tier for the device's pending order","description":"Requires an order already tied to this device (create one via a device order\/store call first). No response body. Requires Accept: application\/json; other callers receive a 302 redirect.","tags":["Activate"],"security":[{"sanctum":[]},{"oauth2":["device.activate"]}],"parameters":[{"name":"device","in":"path","required":true,"description":"Device ULID.","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application\/json":{"schema":{"type":"object","properties":{"plan":{"type":"string","enum":["basic","premium","platinum"]}},"required":["plan"],"additionalProperties":false}}}},"responses":{"204":{"description":"Plan applied to the pending order."},"401":{"$ref":"#\/components\/responses\/Unauthorized"},"403":{"$ref":"#\/components\/responses\/Forbidden"},"404":{"$ref":"#\/components\/responses\/NotFound"},"422":{"$ref":"#\/components\/responses\/ValidationError"}}}},"\/api\/device\/{device}\/order\/plan-duration":{"post":{"operationId":"activate.device-order-plan-duration.store","summary":"Select a billing duration for the device's pending order","description":"Requires an order already tied to this device. No response body. Requires Accept: application\/json; other callers receive a 302 redirect.","tags":["Activate"],"security":[{"sanctum":[]},{"oauth2":["device.activate"]}],"parameters":[{"name":"device","in":"path","required":true,"description":"Device ULID.","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application\/json":{"schema":{"type":"object","properties":{"duration":{"type":"string","enum":["three_days","fourteen_days","one_month","six_months","one_year","lifetime"]}},"required":["duration"],"additionalProperties":false}}}},"responses":{"204":{"description":"Duration applied to the pending order."},"401":{"$ref":"#\/components\/responses\/Unauthorized"},"403":{"$ref":"#\/components\/responses\/Forbidden"},"404":{"$ref":"#\/components\/responses\/NotFound"},"422":{"$ref":"#\/components\/responses\/ValidationError"}}}},"\/api\/device\/{device}\/order\/checkout":{"post":{"operationId":"activate.device-order-checkout.store","summary":"Create a payment + gateway checkout URL for the device's pending order","description":"Requires an order already tied to this device with a GPS product, plan, and duration all already selected (order\/plan and order\/plan-duration). Destructive rewrite, not idempotent: each call recreates the order's line items and replaces the device's subscription row. Requires Accept: application\/json; other callers receive a 302 redirect.","tags":["Activate"],"security":[{"sanctum":[]},{"oauth2":["order.checkout"]}],"parameters":[{"name":"device","in":"path","required":true,"description":"Device ULID.","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application\/json":{"schema":{"type":"object","properties":{"payment_method":{"type":"string","enum":["stripe","cash","wave","orange_money","wire","paypal"]},"success_url":{"type":["string","null"],"format":"uri"}},"required":["payment_method"],"additionalProperties":false}}}},"responses":{"200":{"description":"Pending payment + checkout URL.","content":{"application\/json":{"schema":{"type":"object","properties":{"data":{"$ref":"#\/components\/schemas\/CheckoutResult"}},"additionalProperties":false}}}},"401":{"$ref":"#\/components\/responses\/Unauthorized"},"403":{"$ref":"#\/components\/responses\/Forbidden"},"404":{"$ref":"#\/components\/responses\/NotFound"},"422":{"$ref":"#\/components\/responses\/ValidationError"}}}},"\/api\/device\/{device}\/subscription":{"post":{"operationId":"activate.device-subscription.store","summary":"Create, renew, or upgrade the device's subscription and get a gateway checkout URL","description":"Rate limited. Downgrading an existing subscription applies immediately with no payment step and returns 204 No Content instead of a checkout URL. Requires Accept: application\/json; other callers receive a 302 redirect.","tags":["Activate"],"security":[{"sanctum":[]},{"oauth2":["device.subscription.update"]}],"parameters":[{"name":"device","in":"path","required":true,"description":"Device ULID.","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application\/json":{"schema":{"type":"object","properties":{"plan":{"type":["string","null"],"description":"Keeps the device's current plan when omitted.","enum":["free","basic","premium","platinum"]},"payment_method":{"type":"string","enum":["stripe","cash","wave","orange_money","wire","paypal"]},"billing_period":{"type":["string","null"],"description":"Legacy field. Validated but ignored \u2014 set duration instead."},"duration":{"type":["string","null"],"description":"Defaults to one_year.","enum":["three_days","fourteen_days","one_month","six_months","one_year","lifetime"]},"success_url":{"type":["string","null"],"format":"uri"}},"required":["payment_method"],"additionalProperties":false},"examples":{"default":{"$ref":"#\/components\/examples\/DeviceSubscriptionRequest"}}}}},"responses":{"200":{"description":"Pending payment + checkout URL.","content":{"application\/json":{"schema":{"type":"object","properties":{"data":{"$ref":"#\/components\/schemas\/CheckoutResult"}},"additionalProperties":false},"examples":{"default":{"$ref":"#\/components\/examples\/DeviceSubscriptionResponse"}}}}},"204":{"description":"Downgrade applied immediately \u2014 no payment required."},"401":{"$ref":"#\/components\/responses\/Unauthorized"},"403":{"$ref":"#\/components\/responses\/Forbidden"},"404":{"$ref":"#\/components\/responses\/NotFound"},"422":{"$ref":"#\/components\/responses\/ValidationError"},"429":{"$ref":"#\/components\/responses\/TooManyRequests"}}}},"\/api\/device":{"get":{"operationId":"operate.device.index","summary":"List the caller's device fleet (paginated)","tags":["Operate: Tracking"],"security":[{"sanctum":[]},{"oauth2":["tracking.read"]}],"parameters":[{"name":"unique_ids","in":"query","required":false,"description":"Filter by one or more tracker hardware unique IDs.","schema":{"type":"array","items":{"type":"string"}}},{"name":"q","in":"query","required":false,"description":"Search by device\/vehicle name.","schema":{"type":"string"}},{"name":"categories","in":"query","required":false,"description":"Filter by one or more device category enum values.","schema":{"type":"array","items":{"type":"string"}}},{"name":"disabled","in":"query","required":false,"description":"Filter by disabled state.","schema":{"type":"boolean"}},{"name":"is_setup","in":"query","required":false,"description":"Filter to set-up (true) or pending-setup (false) devices.","schema":{"type":"boolean"}},{"name":"driving","in":"query","required":false,"description":"Filter by ignition-on (true) \/ ignition-off (false).","schema":{"type":"boolean"}},{"name":"online","in":"query","required":false,"description":"Filter by online (true) \/ offline (false) connectivity.","schema":{"type":"boolean"}},{"name":"per_page","in":"query","required":false,"description":"Page size, capped at 100 (default 50).","schema":{"type":"integer"}},{"name":"page","in":"query","required":false,"description":"Page number (1-indexed).","schema":{"type":"integer"}}],"responses":{"200":{"description":"Paginated device list.","content":{"application\/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#\/components\/schemas\/Device"}},"links":{"$ref":"#\/components\/schemas\/PaginationLinks"},"meta":{"$ref":"#\/components\/schemas\/PaginationMeta"}},"additionalProperties":false},"examples":{"default":{"$ref":"#\/components\/examples\/DeviceListResponse"}}}}},"401":{"$ref":"#\/components\/responses\/Unauthorized"},"403":{"$ref":"#\/components\/responses\/Forbidden"}}}},"\/api\/device\/{device}":{"get":{"operationId":"operate.device.show","summary":"Get a single device with its position, subscription, team, and camera device eager-loaded","tags":["Operate: Tracking"],"security":[{"sanctum":[]},{"oauth2":["tracking.read"]}],"parameters":[{"name":"device","in":"path","required":true,"description":"Device ULID.","schema":{"type":"string"}}],"responses":{"200":{"description":"Device detail.","content":{"application\/json":{"schema":{"type":"object","properties":{"data":{"$ref":"#\/components\/schemas\/Device"}},"additionalProperties":false}}}},"401":{"$ref":"#\/components\/responses\/Unauthorized"},"403":{"$ref":"#\/components\/responses\/Forbidden"},"404":{"$ref":"#\/components\/responses\/NotFound"}}},"delete":{"operationId":"operate.device.destroy","summary":"Remove a device from the caller's account","description":"Detaches the caller's ownership only. The device, its history, and its subscription are left intact \u2014 deleting a device is an internal operation, not an API one. Returns 422 when the caller is the device's only owner, it belongs to no team, and it still has an active subscription, because that would leave a paid device nobody can manage.","tags":["Operate: Tracking"],"security":[{"sanctum":[]},{"oauth2":["device.destroy"]}],"parameters":[{"name":"device","in":"path","required":true,"description":"Device ULID.","schema":{"type":"string"}}],"responses":{"204":{"description":"Removed from the caller's account. The device itself still exists."},"401":{"$ref":"#\/components\/responses\/Unauthorized"},"403":{"$ref":"#\/components\/responses\/Forbidden"},"404":{"$ref":"#\/components\/responses\/NotFound"}}}},"\/api\/device\/{device}\/position":{"get":{"operationId":"operate.device-position.index","summary":"List a device's valid GPS position history (not a single latest position, despite the singular path)","tags":["Operate: Tracking"],"security":[{"sanctum":[]},{"oauth2":["tracking.read"]}],"parameters":[{"name":"device","in":"path","required":true,"description":"Device ULID.","schema":{"type":"string"}},{"name":"type","in":"query","required":false,"description":"\"over_speed\" filters to positions recorded at >= 100 km\/h.","schema":{"type":"string"}},{"name":"created_after","in":"query","required":false,"description":"ISO date-time lower bound (fixtime).","schema":{"type":"string","format":"date-time"}},{"name":"created_before","in":"query","required":false,"description":"ISO date-time upper bound (fixtime).","schema":{"type":"string","format":"date-time"}},{"name":"all","in":"query","required":false,"description":"true returns every matching position unpaginated (no links\/meta); omit\/false paginates. Caution: combined with a wide\/absent date range, all=true can return a very large uncapped result set \u2014 always pair it with created_after\/created_before on high-frequency devices.","schema":{"type":"boolean"}},{"name":"per_page","in":"query","required":false,"description":"Page size when all is not true (default 30).","schema":{"type":"integer"}},{"name":"page","in":"query","required":false,"description":"Page number (1-indexed).","schema":{"type":"integer"}}],"responses":{"200":{"description":"Position list, paginated unless all=true.","content":{"application\/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#\/components\/schemas\/Position"}},"links":{"$ref":"#\/components\/schemas\/PaginationLinks"},"meta":{"$ref":"#\/components\/schemas\/PaginationMeta"}},"additionalProperties":false},"examples":{"default":{"$ref":"#\/components\/examples\/DevicePositionListResponse"}}}}},"401":{"$ref":"#\/components\/responses\/Unauthorized"},"403":{"$ref":"#\/components\/responses\/Forbidden"},"404":{"$ref":"#\/components\/responses\/NotFound"}}}},"\/api\/device\/{device}\/replay":{"get":{"operationId":"operate.device-replay.index","summary":"Get a downsampled position+event timeline for map replay over a time window (max 48h)","description":"timeline is a curated merge of downsampled positions and a fixed set of notable event types (ignition, geofence enter\/exit, over-speed, hard braking\/acceleration, stopped\/moving) \u2014 not a raw position or event feed.","tags":["Operate: Tracking"],"security":[{"sanctum":[]},{"oauth2":["tracking.read"]}],"parameters":[{"name":"device","in":"path","required":true,"description":"Device ULID.","schema":{"type":"string"}},{"name":"from","in":"query","required":false,"description":"Window start. Defaults to start of today.","schema":{"type":"string","format":"date-time"}},{"name":"to","in":"query","required":false,"description":"Window end. Defaults to now. Range capped at 48 hours.","schema":{"type":"string","format":"date-time"}},{"name":"resolution","in":"query","required":false,"description":"Downsample resolution in seconds, clamped 5-60 (default 10).","schema":{"type":"integer"}}],"responses":{"200":{"description":"Merged timeline payload.","content":{"application\/json":{"schema":{"type":"object","properties":{"data":{"type":"object","properties":{"timeline":{"type":"array","items":{"type":"object","additionalProperties":true}},"bounds":{"type":["object","null"],"additionalProperties":true},"speed_limit":{"type":["number","null"]},"total_points":{"type":"integer"},"duration_seconds":{"type":"integer"}},"additionalProperties":false}},"required":["data"],"additionalProperties":false}}}},"401":{"$ref":"#\/components\/responses\/Unauthorized"},"403":{"$ref":"#\/components\/responses\/Forbidden"},"404":{"$ref":"#\/components\/responses\/NotFound"}}}},"\/api\/device\/{device}\/trip":{"get":{"operationId":"operate.device-trip.index","summary":"List a device's trips (ignition-on to ignition-off legs) for a given day","description":"Computation is cached per device+day for past days.","tags":["Operate: Tracking"],"security":[{"sanctum":[]},{"oauth2":["tracking.read"]}],"parameters":[{"name":"device","in":"path","required":true,"description":"Device ULID.","schema":{"type":"string"}},{"name":"date","in":"query","required":false,"description":"Day to compute trips for (Y-m-d). Defaults to today.","schema":{"type":"string","format":"date"}}],"responses":{"200":{"description":"Trip legs for the day.","content":{"application\/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"type":"object","additionalProperties":true}}},"required":["data"],"additionalProperties":false}}}},"401":{"$ref":"#\/components\/responses\/Unauthorized"},"403":{"$ref":"#\/components\/responses\/Forbidden"},"404":{"$ref":"#\/components\/responses\/NotFound"}}}},"\/api\/device\/{device}\/event":{"get":{"operationId":"operate.device-event.index","summary":"List a device's alert\/event feed (alarms, ignition, geofence, etc.)","description":"Always excludes device_moving events and manual fuel-leak alarms.","tags":["Operate: Tracking"],"security":[{"sanctum":[]},{"oauth2":["tracking.read"]}],"parameters":[{"name":"device","in":"path","required":true,"description":"Device ULID.","schema":{"type":"string"}},{"name":"types","in":"query","required":false,"description":"Comma-separated (or array) event type enum values to filter to.","schema":{"type":"array","items":{"type":"string"}}},{"name":"created_after","in":"query","required":false,"description":"ISO date-time lower bound (eventtime).","schema":{"type":"string","format":"date-time"}},{"name":"created_before","in":"query","required":false,"description":"ISO date-time upper bound (eventtime).","schema":{"type":"string","format":"date-time"}},{"name":"per_page","in":"query","required":false,"description":"Page size, capped at 100 (default 30).","schema":{"type":"integer"}},{"name":"page","in":"query","required":false,"description":"Page number (1-indexed).","schema":{"type":"integer"}}],"responses":{"200":{"description":"Paginated event list.","content":{"application\/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#\/components\/schemas\/Event"}},"links":{"$ref":"#\/components\/schemas\/PaginationLinks"},"meta":{"$ref":"#\/components\/schemas\/PaginationMeta"}},"additionalProperties":false}}}},"401":{"$ref":"#\/components\/responses\/Unauthorized"},"403":{"$ref":"#\/components\/responses\/Forbidden"},"404":{"$ref":"#\/components\/responses\/NotFound"}}}},"\/api\/device\/{device}\/event\/{event}":{"get":{"operationId":"operate.device-event.show","summary":"Get a single event","tags":["Operate: Tracking"],"security":[{"sanctum":[]},{"oauth2":["tracking.read"]}],"parameters":[{"name":"device","in":"path","required":true,"description":"Device ULID.","schema":{"type":"string"}},{"name":"event","in":"path","required":true,"description":"Event ID.","schema":{"type":"integer"}}],"responses":{"200":{"description":"Event detail.","content":{"application\/json":{"schema":{"type":"object","properties":{"data":{"$ref":"#\/components\/schemas\/Event"}},"additionalProperties":false}}}},"401":{"$ref":"#\/components\/responses\/Unauthorized"},"403":{"$ref":"#\/components\/responses\/Forbidden"},"404":{"$ref":"#\/components\/responses\/NotFound"}}}},"\/api\/device\/{device}\/troubleshooting\/power-off":{"get":{"operationId":"operate.device-troubleshooting-power-off.show","summary":"Get the device's most recent power-cut, low-battery, and inactive\/offline events for power-loss troubleshooting","tags":["Operate: Tracking"],"security":[{"sanctum":[]},{"oauth2":["tracking.read"]}],"parameters":[{"name":"device","in":"path","required":true,"description":"Device ULID.","schema":{"type":"string"}}],"responses":{"200":{"description":"Latest matching event per category.","content":{"application\/json":{"schema":{"type":"object","properties":{"data":{"type":"object","properties":{"power_cut":{"type":"object","description":"The full event shape (see the event schema above) when a matching event exists; {} (empty object, not null) when none exists.","additionalProperties":true},"low_battery":{"type":"object","description":"The full event shape (see the event schema above) when a matching event exists; {} (empty object, not null) when none exists.","additionalProperties":true},"inactive":{"type":"object","description":"The full event shape (see the event schema above) when a matching event exists; {} (empty object, not null) when none exists.","additionalProperties":true}},"required":["power_cut","low_battery","inactive"],"additionalProperties":false}},"required":["data"],"additionalProperties":false}}}},"401":{"$ref":"#\/components\/responses\/Unauthorized"},"403":{"$ref":"#\/components\/responses\/Forbidden"},"404":{"$ref":"#\/components\/responses\/NotFound"}}}},"\/api\/device\/{device}\/acceleration":{"get":{"operationId":"operate.device-acceleration.index","summary":"List harsh-driving (acceleration\/braking\/turning) events for a device","description":"value is the signed delta between the two positions; its magnitude vs. a config threshold determines the \"hard\" classification.","tags":["Operate: Tracking"],"security":[{"sanctum":[]},{"oauth2":["tracking.read"]}],"parameters":[{"name":"device","in":"path","required":true,"description":"Device ULID.","schema":{"type":"string"}},{"name":"created_after","in":"query","required":false,"description":"ISO date-time lower bound.","schema":{"type":"string","format":"date-time"}},{"name":"created_before","in":"query","required":false,"description":"ISO date-time upper bound.","schema":{"type":"string","format":"date-time"}},{"name":"page","in":"query","required":false,"description":"Page number (1-indexed).","schema":{"type":"integer"}},{"name":"per_page","in":"query","required":false,"description":"Page size.","schema":{"type":"integer"}}],"responses":{"200":{"description":"Paginated acceleration event list.","content":{"application\/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"type":"object","properties":{"id":{"type":"integer"},"device_id":{"type":"integer"},"previous_position_id":{"type":["integer","null"]},"previous_value":{"type":["number","null"]},"current_position_id":{"type":["integer","null"]},"current_value":{"type":["number","null"]},"value":{"type":"number"},"delta_time":{"type":["integer","null"],"description":"Seconds between the two positions."},"date":{"type":"string","format":"date-time"},"type":{"type":"string","description":"AccelerationTypeEnum value, e.g. \"speed\" or \"angle\"."}},"additionalProperties":false}},"links":{"$ref":"#\/components\/schemas\/PaginationLinks"},"meta":{"$ref":"#\/components\/schemas\/PaginationMeta"}},"additionalProperties":false}}}},"401":{"$ref":"#\/components\/responses\/Unauthorized"},"403":{"$ref":"#\/components\/responses\/Forbidden"},"404":{"$ref":"#\/components\/responses\/NotFound"}}}},"\/api\/device\/{device}\/command":{"get":{"operationId":"operate.device-command.index","summary":"List command types available to send to this device","tags":["Operate: Tracking"],"security":[{"sanctum":[]},{"oauth2":["tracking.read"]}],"parameters":[{"name":"device","in":"path","required":true,"description":"Device ULID.","schema":{"type":"string"}}],"responses":{"200":{"description":"Command type options.","content":{"application\/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"type":"object","properties":{"value":{"type":"string","description":"Command type, e.g. \"engineStop\", \"alarmArm\"."},"label":{"type":["string","null"]}},"additionalProperties":false}}},"additionalProperties":false}}}},"401":{"$ref":"#\/components\/responses\/Unauthorized"},"403":{"$ref":"#\/components\/responses\/Forbidden"},"404":{"$ref":"#\/components\/responses\/NotFound"}}},"post":{"operationId":"operate.device-command.store","summary":"Send a remote command to the device (engine stop\/resume, alarm arm\/disarm, position request, ...)","description":"Also rate limited, on top of the plan-metered commands allowance below. Plan-metered: consumes the caller's monthly commands allowance (default 500\/month on the free tier) \u2014 429 once exhausted. Metering is skipped entirely (no limit enforced) when the device has no subscription, or a subscription with no plan.","tags":["Operate: Tracking"],"security":[{"sanctum":[]},{"oauth2":["device.alarm","device.immobilize","device.locate"]}],"parameters":[{"name":"device","in":"path","required":true,"description":"Device ULID.","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application\/json":{"schema":{"type":"object","properties":{"command":{"type":"string","description":"Command type, e.g. \"engineStop\", \"alarmArm\"."}},"required":["command"],"additionalProperties":false},"examples":{"default":{"$ref":"#\/components\/examples\/DeviceCommandRequest"}}}}},"responses":{"200":{"description":"Command result.","content":{"application\/json":{"schema":{"type":"object","properties":{"status":{"type":"string"},"message":{"type":"string"},"delivered_via":{"type":"string","description":"Delivery path used, e.g. \"ingestor\" or \"queued\"."}},"required":["status","message","delivered_via"],"additionalProperties":false},"examples":{"default":{"$ref":"#\/components\/examples\/DeviceCommandResponse"}}}}},"401":{"$ref":"#\/components\/responses\/Unauthorized"},"403":{"$ref":"#\/components\/responses\/Forbidden"},"404":{"$ref":"#\/components\/responses\/NotFound"},"422":{"$ref":"#\/components\/responses\/ValidationError"},"429":{"$ref":"#\/components\/responses\/TooManyRequests"}}}},"\/api\/device\/{device}\/diagnose":{"get":{"operationId":"operate.device-diagnose.stream","summary":"Stream a progressive GPS\/connectivity\/behavior diagnostic as Server-Sent Events","description":"Content-Type: text\/event-stream \u2014 not a JSON response. Events, in order: repeated \"section\" ({key, severity, status, ...} per diagnostic section, cheapest-first), one \"hypotheses\" (cross-signal correlation list), then one terminal \"complete\" ({overall_severity, summary}). An \"error\" event replaces the remaining stream on unrecoverable failure. Heartbeats (\": keep-alive\") are sent every ~15s during the heavy lag\/trip compute so proxies do not drop the connection.","tags":["Operate: Tracking"],"security":[{"sanctum":[]},{"oauth2":["tracking.read"]}],"parameters":[{"name":"device","in":"path","required":true,"description":"Device ULID.","schema":{"type":"string"}}],"responses":{"200":{"description":"SSE stream of diagnostic events.","content":{"text\/event-stream":{"schema":{"type":"string","description":"Raw SSE frames; no single JSON schema applies (see operation description for the per-event-type shapes)."}}}},"401":{"$ref":"#\/components\/responses\/Unauthorized"},"403":{"$ref":"#\/components\/responses\/Forbidden"},"404":{"$ref":"#\/components\/responses\/NotFound"}}}},"\/api\/device\/{device}\/odometer":{"get":{"operationId":"operate.device-odometer.show","summary":"Get the device's odometer reading (meters) as of a date","tags":["Operate: Reports"],"security":[{"sanctum":[]},{"oauth2":["report.read"]}],"parameters":[{"name":"device","in":"path","required":true,"description":"Device ULID.","schema":{"type":"string"}},{"name":"date","in":"query","required":false,"description":"Date\/time to read the odometer at. Defaults to now.","schema":{"type":"string","format":"date-time"}}],"responses":{"200":{"description":"Odometer reading.","content":{"application\/json":{"schema":{"type":"object","properties":{"data":{"type":"object","description":"odometer is in meters.","properties":{"odometer":{"type":"number"}},"required":["odometer"],"additionalProperties":false}},"required":["data"],"additionalProperties":false}}}},"401":{"$ref":"#\/components\/responses\/Unauthorized"},"403":{"$ref":"#\/components\/responses\/Forbidden"},"404":{"$ref":"#\/components\/responses\/NotFound"}}}},"\/api\/device\/{device}\/distance":{"get":{"operationId":"operate.device-distance.show","summary":"Get the distance driven between two odometer read dates","description":"Despite the names, odometer_from\/odometer_until are dates, not odometer readings \u2014 each is resolved to an odometer value first.","tags":["Operate: Reports"],"security":[{"sanctum":[]},{"oauth2":["report.read"]}],"parameters":[{"name":"device","in":"path","required":true,"description":"Device ULID.","schema":{"type":"string"}},{"name":"odometer_from","in":"query","required":true,"description":"Start date\/time to read the odometer at.","schema":{"type":"string","format":"date-time"}},{"name":"odometer_until","in":"query","required":false,"description":"End date\/time to read the odometer at. Defaults to now.","schema":{"type":"string","format":"date-time"}}],"responses":{"200":{"description":"Odometer readings + delta.","content":{"application\/json":{"schema":{"type":"object","properties":{"data":{"type":"object","description":"All three fields are in meters; distance is odometer_until minus odometer_from.","properties":{"odometer_from":{"type":"number"},"odometer_until":{"type":"number"},"distance":{"type":"number"}},"required":["odometer_from","odometer_until","distance"],"additionalProperties":false}},"required":["data"],"additionalProperties":false}}}},"401":{"$ref":"#\/components\/responses\/Unauthorized"},"403":{"$ref":"#\/components\/responses\/Forbidden"},"404":{"$ref":"#\/components\/responses\/NotFound"}}}},"\/api\/device\/{device}\/reports\/time-series":{"get":{"operationId":"operate.device-reports-time-series.show","summary":"Get a time-bucketed report (fuel, mileage, speed, engine hours, acceleration, braking, or turning)","description":"Plan-metered: consumes the caller's monthly reports allowance \u2014 429 once exhausted. Granularity caps the max range: minute <= 24h, hour <= 30d, day <= 1y.","tags":["Operate: Reports"],"security":[{"sanctum":[]},{"oauth2":["report.read"]}],"parameters":[{"name":"device","in":"path","required":true,"description":"Device ULID.","schema":{"type":"string"}},{"name":"report_type","in":"query","required":true,"description":"ReportTypeEnum value.","schema":{"type":"string","enum":["fuel","mileage","speed","engine_hours","acceleration","braking","turning"]}},{"name":"granularity","in":"query","required":false,"description":"Defaults to hour.","schema":{"type":"string","enum":["minute","hour","day"]}},{"name":"from","in":"query","required":false,"description":"Range start. Defaults to 1 day ago.","schema":{"type":"string","format":"date-time"}},{"name":"to","in":"query","required":false,"description":"Range end (must be >= from). Defaults to now.","schema":{"type":"string","format":"date-time"}}],"responses":{"200":{"description":"Time-series buckets + echoed request meta.","content":{"application\/json":{"schema":{"type":"object","properties":{"data":{"type":"object","description":"Report-type-specific bucketed series; includes a \"labels\" array used to compute meta.total_points.","additionalProperties":true},"meta":{"type":"object","properties":{"report_type":{"type":"string"},"granularity":{"type":"string"},"from":{"type":"string","format":"date-time"},"to":{"type":"string","format":"date-time"},"total_points":{"type":"integer"}},"required":["report_type","granularity","from","to","total_points"],"additionalProperties":false}},"required":["data","meta"],"additionalProperties":false}}}},"401":{"$ref":"#\/components\/responses\/Unauthorized"},"403":{"$ref":"#\/components\/responses\/Forbidden"},"404":{"$ref":"#\/components\/responses\/NotFound"},"429":{"$ref":"#\/components\/responses\/TooManyRequests"}}}},"\/api\/device\/{device}\/fuel":{"post":{"operationId":"operate.device-fuel.store","summary":"Log a fuel refill for the device, recalculating its odometer-linked fuel level","description":"amount is the fuel added, in the device's fuel_unit (liters\/gallons) \u2014 not a monetary amount. 400 (not 422) on failure, e.g. no fuel sensor configured \u2014 response body is {type: \"error\", message}.","tags":["Operate: Reports"],"security":[{"sanctum":[]},{"oauth2":["maintenance.write"]}],"parameters":[{"name":"device","in":"path","required":true,"description":"Device ULID.","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application\/json":{"schema":{"type":"object","properties":{"date":{"type":"string","format":"date-time","description":"When the refill happened."},"amount":{"type":"number"}},"required":["date","amount"],"additionalProperties":false}}}},"responses":{"200":{"description":"Recalculated odometer.","content":{"application\/json":{"schema":{"type":"object","properties":{"data":{"type":"object","properties":{"odometer":{"type":["number","null"]}},"required":["odometer"],"additionalProperties":false}},"required":["data"],"additionalProperties":false}}}},"401":{"$ref":"#\/components\/responses\/Unauthorized"},"403":{"$ref":"#\/components\/responses\/Forbidden"},"404":{"$ref":"#\/components\/responses\/NotFound"},"422":{"$ref":"#\/components\/responses\/ValidationError"}}}},"\/api\/geofence":{"get":{"operationId":"operate.geofence.index","summary":"List the caller's geofences, optionally scoped to one device","tags":["Operate: Geofences"],"security":[{"sanctum":[]},{"oauth2":["geofence.read"]}],"parameters":[{"name":"device_id","in":"query","required":false,"description":"Device ULID to scope to the fences visible to that device (own + shared).","schema":{"type":"string"}},{"name":"page","in":"query","required":false,"description":"Page number (1-indexed).","schema":{"type":"integer"}},{"name":"per_page","in":"query","required":false,"description":"Page size.","schema":{"type":"integer"}}],"responses":{"200":{"description":"Paginated geofence list.","content":{"application\/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#\/components\/schemas\/Geofence"}},"links":{"$ref":"#\/components\/schemas\/PaginationLinks"},"meta":{"$ref":"#\/components\/schemas\/PaginationMeta"}},"additionalProperties":false}}}},"401":{"$ref":"#\/components\/responses\/Unauthorized"},"403":{"$ref":"#\/components\/responses\/Forbidden"}}},"post":{"operationId":"operate.geofence.store","summary":"Create an account-level geofence, optionally attaching it to a device","description":"Consumes the caller's geo-zones allowance (plan-metered, lifetime total) \u2014 429 once exhausted (no pre-check gate here, unlike POST \/api\/device\/{device}\/geo-zone).","tags":["Operate: Geofences"],"security":[{"sanctum":[]},{"oauth2":["geofence.write"]}],"parameters":[],"requestBody":{"required":true,"content":{"application\/json":{"schema":{"type":"object","properties":{"name":{"type":"string"},"description":{"type":["string","null"]},"area":{"type":"string","description":"WKT geometry: \"CIRCLE(lat lon, radiusMeters)\" (radius >= 100m) or \"POLYGON((lat1 lon1, lat2 lon2, ...))\"."},"device_id":{"type":["string","null"],"description":"Device ULID to attach this fence to on creation."}},"required":["name","area"],"additionalProperties":false}}}},"responses":{"200":{"description":"Created geofence.","content":{"application\/json":{"schema":{"type":"object","properties":{"data":{"$ref":"#\/components\/schemas\/Geofence"}},"additionalProperties":false}}}},"401":{"$ref":"#\/components\/responses\/Unauthorized"},"403":{"$ref":"#\/components\/responses\/Forbidden"},"422":{"$ref":"#\/components\/responses\/ValidationError"},"429":{"$ref":"#\/components\/responses\/TooManyRequests"}}}},"\/api\/geofence\/{geofence}":{"get":{"operationId":"operate.geofence.show","summary":"Get a geofence","tags":["Operate: Geofences"],"security":[{"sanctum":[]},{"oauth2":["geofence.read"]}],"parameters":[{"name":"geofence","in":"path","required":true,"description":"Geofence ID.","schema":{"type":"integer"}},{"name":"device_id","in":"query","required":false,"description":"Device ULID; when set, the fence is looked up among that device's fences instead of the caller's own.","schema":{"type":"string"}}],"responses":{"200":{"description":"Geofence detail.","content":{"application\/json":{"schema":{"type":"object","properties":{"data":{"$ref":"#\/components\/schemas\/Geofence"}},"additionalProperties":false}}}},"401":{"$ref":"#\/components\/responses\/Unauthorized"},"403":{"$ref":"#\/components\/responses\/Forbidden"},"404":{"$ref":"#\/components\/responses\/NotFound"}}},"put":{"operationId":"operate.geofence.update","summary":"Update a geofence's name, description, boundary, and\/or attributes","description":"All fields optional (partial update); area preserved when absent.","tags":["Operate: Geofences"],"security":[{"sanctum":[]},{"oauth2":["geofence.write"]}],"parameters":[{"name":"geofence","in":"path","required":true,"description":"Geofence ID.","schema":{"type":"integer"}},{"name":"device_id","in":"query","required":false,"description":"Device ULID; when set, the fence is looked up among that device's fences instead of the caller's own.","schema":{"type":"string"}}],"requestBody":{"required":false,"content":{"application\/json":{"schema":{"type":"object","properties":{"name":{"type":"string"},"description":{"type":["string","null"]},"area":{"type":"string","description":"WKT geometry, same format as store. Omit to keep the stored boundary (e.g. an attributes-only update)."},"attributes":{"type":"object","description":"Merged onto the stored attributes bag \u2014 omitted keys are preserved, not wiped.","additionalProperties":true}},"additionalProperties":false}}}},"responses":{"200":{"description":"Updated geofence.","content":{"application\/json":{"schema":{"type":"object","properties":{"data":{"$ref":"#\/components\/schemas\/Geofence"}},"additionalProperties":false}}}},"401":{"$ref":"#\/components\/responses\/Unauthorized"},"403":{"$ref":"#\/components\/responses\/Forbidden"},"404":{"$ref":"#\/components\/responses\/NotFound"},"422":{"$ref":"#\/components\/responses\/ValidationError"}}},"delete":{"operationId":"operate.geofence.destroy","summary":"Delete a geofence","tags":["Operate: Geofences"],"security":[{"sanctum":[]},{"oauth2":["geofence.write"]}],"parameters":[{"name":"geofence","in":"path","required":true,"description":"Geofence ID.","schema":{"type":"integer"}},{"name":"device_id","in":"query","required":false,"description":"Device ULID; when set, the fence is looked up among that device's fences instead of the caller's own.","schema":{"type":"string"}}],"responses":{"204":{"description":"Deleted; releases one geo-zones allowance unit."},"401":{"$ref":"#\/components\/responses\/Unauthorized"},"403":{"$ref":"#\/components\/responses\/Forbidden"},"404":{"$ref":"#\/components\/responses\/NotFound"}}}},"\/api\/device\/{device}\/geo-zone":{"get":{"operationId":"operate.device-geo-zone.index","summary":"List geofences visible to a device (its own + the caller's + shared via team)","tags":["Operate: Geofences"],"security":[{"sanctum":[]},{"oauth2":["geofence.read"]}],"parameters":[{"name":"device","in":"path","required":true,"description":"Device ULID.","schema":{"type":"string"}},{"name":"page","in":"query","required":false,"description":"Page number (1-indexed).","schema":{"type":"integer"}},{"name":"per_page","in":"query","required":false,"description":"Page size.","schema":{"type":"integer"}}],"responses":{"200":{"description":"Paginated geo-zone list.","content":{"application\/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#\/components\/schemas\/Geofence"}},"links":{"$ref":"#\/components\/schemas\/PaginationLinks"},"meta":{"$ref":"#\/components\/schemas\/PaginationMeta"}},"additionalProperties":false}}}},"401":{"$ref":"#\/components\/responses\/Unauthorized"},"403":{"$ref":"#\/components\/responses\/Forbidden"},"404":{"$ref":"#\/components\/responses\/NotFound"}}},"post":{"operationId":"operate.device-geo-zone.store","summary":"Create a geo-zone on a device from decomposed lat\/lng\/radius (circle) or a points array (polygon)","description":"Unlike POST \/api\/geofence, the boundary here is decomposed fields, not a raw WKT \"area\" string. latitude\/longitude\/radius (meters, 100-50000) are required when type is circle; points is required when type is polygon. The resulting WKT boundary is capped at 8192 characters (422 with a \"too many points\" error past that \u2014 mainly a polygon concern). Consumes the caller's geo-zones allowance; 422 once at cap.","tags":["Operate: Geofences"],"security":[{"sanctum":[]},{"oauth2":["geofence.write"]}],"parameters":[{"name":"device","in":"path","required":true,"description":"Device ULID.","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application\/json":{"schema":{"type":"object","properties":{"type":{"type":"string","description":"GeofenceAreaTypeEnum.","enum":["circle","polygon"]},"latitude":{"type":"number"},"longitude":{"type":"number"},"radius":{"type":"number"},"points":{"type":"array","items":{"type":"object","properties":{"latitude":{"type":"number"},"longitude":{"type":"number"}},"required":["latitude","longitude"],"additionalProperties":false}},"name":{"type":"string"},"description":{"type":["string","null"]},"color":{"type":["string","null"]},"color_opacity":{"type":["number","null"]},"zones":{"type":["object","null"],"additionalProperties":true}},"required":["name","type"],"additionalProperties":false}}}},"responses":{"200":{"description":"Created geo-zone.","content":{"application\/json":{"schema":{"type":"object","properties":{"data":{"$ref":"#\/components\/schemas\/Geofence"}},"additionalProperties":false}}}},"401":{"$ref":"#\/components\/responses\/Unauthorized"},"403":{"$ref":"#\/components\/responses\/Forbidden"},"404":{"$ref":"#\/components\/responses\/NotFound"},"422":{"$ref":"#\/components\/responses\/ValidationError"}}}},"\/api\/device\/{device}\/geo-zone\/{geo_zone}":{"get":{"operationId":"operate.device-geo-zone.show","summary":"Get a device geo-zone","tags":["Operate: Geofences"],"security":[{"sanctum":[]},{"oauth2":["geofence.read"]}],"parameters":[{"name":"device","in":"path","required":true,"description":"Device ULID.","schema":{"type":"string"}},{"name":"geo_zone","in":"path","required":true,"description":"Geofence ID.","schema":{"type":"integer"}}],"responses":{"200":{"description":"Geo-zone detail.","content":{"application\/json":{"schema":{"type":"object","properties":{"data":{"$ref":"#\/components\/schemas\/Geofence"}},"additionalProperties":false}}}},"401":{"$ref":"#\/components\/responses\/Unauthorized"},"403":{"$ref":"#\/components\/responses\/Forbidden"},"404":{"$ref":"#\/components\/responses\/NotFound"}}},"put":{"operationId":"operate.device-geo-zone.update","summary":"Update a device geo-zone's name, boundary, and\/or alert attributes","description":"All fields optional \u2014 e.g. the mobile alerts toggle sends only {attributes: {alerts_enabled}} without touching name\/boundary. Boundary preserved when type is absent; when type is present, latitude\/longitude\/radius (meters, 100-50000) are required when type is circle; points is required when type is polygon. The resulting WKT boundary is capped at 8192 characters (422 with a \"too many points\" error past that \u2014 mainly a polygon concern).","tags":["Operate: Geofences"],"security":[{"sanctum":[]},{"oauth2":["geofence.write"]}],"parameters":[{"name":"device","in":"path","required":true,"description":"Device ULID.","schema":{"type":"string"}},{"name":"geo_zone","in":"path","required":true,"description":"Geofence ID.","schema":{"type":"integer"}}],"requestBody":{"required":false,"content":{"application\/json":{"schema":{"type":"object","properties":{"type":{"type":"string","description":"GeofenceAreaTypeEnum.","enum":["circle","polygon"]},"latitude":{"type":"number"},"longitude":{"type":"number"},"radius":{"type":"number"},"points":{"type":"array","items":{"type":"object","properties":{"latitude":{"type":"number"},"longitude":{"type":"number"}},"required":["latitude","longitude"],"additionalProperties":false}},"name":{"type":"string"},"description":{"type":["string","null"]},"attributes":{"type":"object","description":"Merged onto the stored attributes bag.","properties":{"alerts_enabled":{"type":"boolean"}},"additionalProperties":true}},"additionalProperties":false}}}},"responses":{"200":{"description":"Updated geo-zone.","content":{"application\/json":{"schema":{"type":"object","properties":{"data":{"$ref":"#\/components\/schemas\/Geofence"}},"additionalProperties":false}}}},"401":{"$ref":"#\/components\/responses\/Unauthorized"},"403":{"$ref":"#\/components\/responses\/Forbidden"},"404":{"$ref":"#\/components\/responses\/NotFound"},"422":{"$ref":"#\/components\/responses\/ValidationError"}}},"delete":{"operationId":"operate.device-geo-zone.destroy","summary":"Delete a device geo-zone","tags":["Operate: Geofences"],"security":[{"sanctum":[]},{"oauth2":["geofence.write"]}],"parameters":[{"name":"device","in":"path","required":true,"description":"Device ULID.","schema":{"type":"string"}},{"name":"geo_zone","in":"path","required":true,"description":"Geofence ID.","schema":{"type":"integer"}}],"responses":{"204":{"description":"Deleted; releases one geo-zones allowance unit."},"401":{"$ref":"#\/components\/responses\/Unauthorized"},"403":{"$ref":"#\/components\/responses\/Forbidden"},"404":{"$ref":"#\/components\/responses\/NotFound"}}}},"\/api\/poi":{"get":{"operationId":"operate.poi.index","summary":"List the caller's points of interest (places with repeat stops), ranked by total dwell time","description":"No create endpoint \u2014 POIs are auto-computed from position visit clustering, not user-authored. Cursor-paginated (per_page\/cursor), unlike the offset-paginated (page\/per_page) list endpoints elsewhere in this API.","tags":["Operate: Geofences"],"security":[{"sanctum":[]},{"oauth2":["geofence.read"]}],"parameters":[{"name":"device_id","in":"query","required":false,"description":"Device ULID to scope POIs to that device's visits.","schema":{"type":"string"}},{"name":"from","in":"query","required":false,"description":"Visit window start.","schema":{"type":"string","format":"date-time"}},{"name":"to","in":"query","required":false,"description":"Visit window end (>= from).","schema":{"type":"string","format":"date-time"}},{"name":"per_page","in":"query","required":false,"description":"Page size, 1-100 (default 20).","schema":{"type":"integer"}},{"name":"cursor","in":"query","required":false,"description":"Cursor-paginated (not offset) \u2014 pass the previous response's next cursor.","schema":{"type":"string"}}],"responses":{"200":{"description":"Cursor-paginated POI list, plus meta.is_at_cap for the caller's geo-zone allowance.","content":{"application\/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#\/components\/schemas\/Poi"}},"links":{"$ref":"#\/components\/schemas\/PaginationLinks"},"meta":{"type":"object","properties":{"is_at_cap":{"type":"boolean"}},"additionalProperties":true}},"required":["data"],"additionalProperties":false}}}},"401":{"$ref":"#\/components\/responses\/Unauthorized"},"403":{"$ref":"#\/components\/responses\/Forbidden"}}}},"\/api\/poi\/{poi}":{"get":{"operationId":"operate.poi.show","summary":"Get a POI with its visit history","tags":["Operate: Geofences"],"security":[{"sanctum":[]},{"oauth2":["geofence.read"]}],"parameters":[{"name":"poi","in":"path","required":true,"description":"POI ID.","schema":{"type":"string"}}],"responses":{"200":{"description":"POI detail.","content":{"application\/json":{"schema":{"type":"object","properties":{"data":{"$ref":"#\/components\/schemas\/Poi"}},"additionalProperties":false}}}},"401":{"$ref":"#\/components\/responses\/Unauthorized"},"403":{"$ref":"#\/components\/responses\/Forbidden"},"404":{"$ref":"#\/components\/responses\/NotFound"}}},"put":{"operationId":"operate.poi.update","summary":"Rename a POI","tags":["Operate: Geofences"],"security":[{"sanctum":[]},{"oauth2":["geofence.write"]}],"parameters":[{"name":"poi","in":"path","required":true,"description":"POI ID.","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application\/json":{"schema":{"type":"object","properties":{"name":{"type":"string"}},"required":["name"],"additionalProperties":false}}}},"responses":{"200":{"description":"Updated POI.","content":{"application\/json":{"schema":{"type":"object","properties":{"data":{"$ref":"#\/components\/schemas\/Poi"}},"additionalProperties":false}}}},"401":{"$ref":"#\/components\/responses\/Unauthorized"},"403":{"$ref":"#\/components\/responses\/Forbidden"},"404":{"$ref":"#\/components\/responses\/NotFound"},"422":{"$ref":"#\/components\/responses\/ValidationError"}}},"delete":{"operationId":"operate.poi.destroy","summary":"Delete a POI","tags":["Operate: Geofences"],"security":[{"sanctum":[]},{"oauth2":["geofence.write"]}],"parameters":[{"name":"poi","in":"path","required":true,"description":"POI ID.","schema":{"type":"string"}}],"responses":{"204":{"description":"Deleted."},"401":{"$ref":"#\/components\/responses\/Unauthorized"},"403":{"$ref":"#\/components\/responses\/Forbidden"},"404":{"$ref":"#\/components\/responses\/NotFound"}}}},"\/api\/poi\/{poi}\/visit":{"get":{"operationId":"operate.poi-visit.index","summary":"List a POI's visit history across all of the caller's devices","tags":["Operate: Geofences"],"security":[{"sanctum":[]},{"oauth2":["geofence.read"]}],"parameters":[{"name":"poi","in":"path","required":true,"description":"POI ID.","schema":{"type":"string"}},{"name":"per_page","in":"query","required":false,"description":"Page size, 1-100 (default 20).","schema":{"type":"integer"}},{"name":"page","in":"query","required":false,"description":"Page number.","schema":{"type":"integer"}}],"responses":{"200":{"description":"Paginated visit list.","content":{"application\/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#\/components\/schemas\/PoiVisit"}},"links":{"$ref":"#\/components\/schemas\/PaginationLinks"},"meta":{"$ref":"#\/components\/schemas\/PaginationMeta"}},"additionalProperties":false}}}},"401":{"$ref":"#\/components\/responses\/Unauthorized"},"403":{"$ref":"#\/components\/responses\/Forbidden"},"404":{"$ref":"#\/components\/responses\/NotFound"}}}},"\/api\/device\/{device}\/poi":{"get":{"operationId":"operate.device-poi.index","summary":"List points of interest a specific device has visited, ranked by total dwell time","tags":["Operate: Geofences"],"security":[{"sanctum":[]},{"oauth2":["geofence.read"]}],"parameters":[{"name":"device","in":"path","required":true,"description":"Device ULID.","schema":{"type":"string"}}],"responses":{"200":{"description":"Paginated POI list, plus meta.is_at_cap.","content":{"application\/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#\/components\/schemas\/Poi"}},"links":{"$ref":"#\/components\/schemas\/PaginationLinks"},"meta":{"type":"object","properties":{"is_at_cap":{"type":"boolean"}},"additionalProperties":true}},"required":["data"],"additionalProperties":false}}}},"401":{"$ref":"#\/components\/responses\/Unauthorized"},"403":{"$ref":"#\/components\/responses\/Forbidden"},"404":{"$ref":"#\/components\/responses\/NotFound"}}}},"\/api\/device\/{device}\/poi\/{poi}":{"get":{"operationId":"operate.device-poi.show","summary":"Get a POI scoped to this device's visits","tags":["Operate: Geofences"],"security":[{"sanctum":[]},{"oauth2":["geofence.read"]}],"parameters":[{"name":"device","in":"path","required":true,"description":"Device ULID.","schema":{"type":"string"}},{"name":"poi","in":"path","required":true,"description":"POI ID.","schema":{"type":"string"}}],"responses":{"200":{"description":"POI detail.","content":{"application\/json":{"schema":{"type":"object","properties":{"data":{"$ref":"#\/components\/schemas\/Poi"}},"additionalProperties":false}}}},"401":{"$ref":"#\/components\/responses\/Unauthorized"},"403":{"$ref":"#\/components\/responses\/Forbidden"},"404":{"$ref":"#\/components\/responses\/NotFound"}}},"put":{"operationId":"operate.device-poi.update","summary":"Rename a POI","tags":["Operate: Geofences"],"security":[{"sanctum":[]},{"oauth2":["geofence.write"]}],"parameters":[{"name":"device","in":"path","required":true,"description":"Device ULID.","schema":{"type":"string"}},{"name":"poi","in":"path","required":true,"description":"POI ID.","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application\/json":{"schema":{"type":"object","properties":{"name":{"type":"string"}},"required":["name"],"additionalProperties":false}}}},"responses":{"200":{"description":"Updated POI.","content":{"application\/json":{"schema":{"type":"object","properties":{"data":{"$ref":"#\/components\/schemas\/Poi"}},"additionalProperties":false}}}},"401":{"$ref":"#\/components\/responses\/Unauthorized"},"403":{"$ref":"#\/components\/responses\/Forbidden"},"404":{"$ref":"#\/components\/responses\/NotFound"},"422":{"$ref":"#\/components\/responses\/ValidationError"}}},"delete":{"operationId":"operate.device-poi.destroy","summary":"Delete a POI","tags":["Operate: Geofences"],"security":[{"sanctum":[]},{"oauth2":["geofence.write"]}],"parameters":[{"name":"device","in":"path","required":true,"description":"Device ULID.","schema":{"type":"string"}},{"name":"poi","in":"path","required":true,"description":"POI ID.","schema":{"type":"string"}}],"responses":{"204":{"description":"Deleted."},"401":{"$ref":"#\/components\/responses\/Unauthorized"},"403":{"$ref":"#\/components\/responses\/Forbidden"},"404":{"$ref":"#\/components\/responses\/NotFound"}}}},"\/api\/device\/{device}\/poi\/{poi}\/visit":{"get":{"operationId":"operate.device-poi-visit.index","summary":"List a POI's visits made by this specific device","tags":["Operate: Geofences"],"security":[{"sanctum":[]},{"oauth2":["geofence.read"]}],"parameters":[{"name":"device","in":"path","required":true,"description":"Device ULID.","schema":{"type":"string"}},{"name":"poi","in":"path","required":true,"description":"POI ID.","schema":{"type":"string"}},{"name":"per_page","in":"query","required":false,"description":"Page size, 1-100 (default 20).","schema":{"type":"integer"}},{"name":"page","in":"query","required":false,"description":"Page number.","schema":{"type":"integer"}}],"responses":{"200":{"description":"Paginated visit list.","content":{"application\/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#\/components\/schemas\/PoiVisit"}},"links":{"$ref":"#\/components\/schemas\/PaginationLinks"},"meta":{"$ref":"#\/components\/schemas\/PaginationMeta"}},"additionalProperties":false}}}},"401":{"$ref":"#\/components\/responses\/Unauthorized"},"403":{"$ref":"#\/components\/responses\/Forbidden"},"404":{"$ref":"#\/components\/responses\/NotFound"}}}},"\/api\/device\/{device}\/inspection":{"get":{"operationId":"operate.device-inspection.index","summary":"List a device's inspections","tags":["Operate: Maintenance"],"security":[{"sanctum":[]},{"oauth2":["maintenance.read"]}],"parameters":[{"name":"device","in":"path","required":true,"description":"Device ULID.","schema":{"type":"string"}},{"name":"page","in":"query","required":false,"description":"Page number (1-indexed).","schema":{"type":"integer"}},{"name":"per_page","in":"query","required":false,"description":"Page size.","schema":{"type":"integer"}}],"responses":{"200":{"description":"Paginated inspection list.","content":{"application\/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#\/components\/schemas\/DeviceInspection"}},"links":{"$ref":"#\/components\/schemas\/PaginationLinks"},"meta":{"$ref":"#\/components\/schemas\/PaginationMeta"}},"additionalProperties":false}}}},"401":{"$ref":"#\/components\/responses\/Unauthorized"},"403":{"$ref":"#\/components\/responses\/Forbidden"},"404":{"$ref":"#\/components\/responses\/NotFound"}}},"post":{"operationId":"operate.device-inspection.store","summary":"Record a vehicle inspection","tags":["Operate: Maintenance"],"security":[{"sanctum":[]},{"oauth2":["maintenance.write"]}],"parameters":[{"name":"device","in":"path","required":true,"description":"Device ULID.","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application\/json":{"schema":{"type":"object","properties":{"summary_condition":{"type":"string","description":"VehicleConditionEnum.","enum":["excellent","good","fair","poor","damaged"]},"notes":{"type":"string"},"inspector_id":{"type":["integer","null"],"description":"Ignored \u2014 the inspection is always attributed to the authenticated caller regardless of this field."}},"required":["summary_condition","notes"],"additionalProperties":false}}}},"responses":{"200":{"description":"Created inspection.","content":{"application\/json":{"schema":{"type":"object","properties":{"data":{"$ref":"#\/components\/schemas\/DeviceInspection"}},"additionalProperties":false}}}},"401":{"$ref":"#\/components\/responses\/Unauthorized"},"403":{"$ref":"#\/components\/responses\/Forbidden"},"404":{"$ref":"#\/components\/responses\/NotFound"},"422":{"$ref":"#\/components\/responses\/ValidationError"}}}},"\/api\/device\/{device}\/inspection\/{inspection}":{"delete":{"operationId":"operate.device-inspection.destroy","summary":"Delete an inspection","tags":["Operate: Maintenance"],"security":[{"sanctum":[]},{"oauth2":["maintenance.write"]}],"parameters":[{"name":"device","in":"path","required":true,"description":"Device ULID.","schema":{"type":"string"}},{"name":"inspection","in":"path","required":true,"description":"Inspection ID.","schema":{"type":"string"}}],"responses":{"204":{"description":"Deleted."},"401":{"$ref":"#\/components\/responses\/Unauthorized"},"403":{"$ref":"#\/components\/responses\/Forbidden"},"404":{"$ref":"#\/components\/responses\/NotFound"}}}},"\/api\/device\/{device}\/document":{"get":{"operationId":"operate.device-document.index","summary":"List a device's documents (insurance, registration, etc.)","tags":["Operate: Maintenance"],"security":[{"sanctum":[]},{"oauth2":["maintenance.read"]}],"parameters":[{"name":"device","in":"path","required":true,"description":"Device ULID.","schema":{"type":"string"}},{"name":"page","in":"query","required":false,"description":"Page number (1-indexed).","schema":{"type":"integer"}},{"name":"per_page","in":"query","required":false,"description":"Page size.","schema":{"type":"integer"}}],"responses":{"200":{"description":"Paginated document list.","content":{"application\/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#\/components\/schemas\/DeviceDocument"}},"links":{"$ref":"#\/components\/schemas\/PaginationLinks"},"meta":{"$ref":"#\/components\/schemas\/PaginationMeta"}},"additionalProperties":false}}}},"401":{"$ref":"#\/components\/responses\/Unauthorized"},"403":{"$ref":"#\/components\/responses\/Forbidden"},"404":{"$ref":"#\/components\/responses\/NotFound"}}},"post":{"operationId":"operate.device-document.store","summary":"Create a document record for a device (media uploaded separately via device-document\/{document}\/media, out of scope here)","description":"Plan-metered: consumes the caller's documents allowance \u2014 429 once exhausted.","tags":["Operate: Maintenance"],"security":[{"sanctum":[]},{"oauth2":["maintenance.write"]}],"parameters":[{"name":"device","in":"path","required":true,"description":"Device ULID.","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application\/json":{"schema":{"type":"object","properties":{"name":{"type":["string","null"]},"expires_at":{"type":["string","null"],"format":"date","description":"Must be a future date."}},"additionalProperties":false}}}},"responses":{"200":{"description":"Created document.","content":{"application\/json":{"schema":{"type":"object","properties":{"data":{"$ref":"#\/components\/schemas\/DeviceDocument"}},"additionalProperties":false}}}},"401":{"$ref":"#\/components\/responses\/Unauthorized"},"403":{"$ref":"#\/components\/responses\/Forbidden"},"404":{"$ref":"#\/components\/responses\/NotFound"},"422":{"$ref":"#\/components\/responses\/ValidationError"},"429":{"$ref":"#\/components\/responses\/TooManyRequests"}}}},"\/api\/device\/{device}\/document\/{document}":{"get":{"operationId":"operate.device-document.show","summary":"Get a document","tags":["Operate: Maintenance"],"security":[{"sanctum":[]},{"oauth2":["maintenance.read"]}],"parameters":[{"name":"device","in":"path","required":true,"description":"Device ULID.","schema":{"type":"string"}},{"name":"document","in":"path","required":true,"description":"Document ID.","schema":{"type":"string"}}],"responses":{"200":{"description":"Document detail.","content":{"application\/json":{"schema":{"type":"object","properties":{"data":{"$ref":"#\/components\/schemas\/DeviceDocument"}},"additionalProperties":false}}}},"401":{"$ref":"#\/components\/responses\/Unauthorized"},"403":{"$ref":"#\/components\/responses\/Forbidden"},"404":{"$ref":"#\/components\/responses\/NotFound"}}},"put":{"operationId":"operate.device-document.update","summary":"Update a document's name\/expiry","tags":["Operate: Maintenance"],"security":[{"sanctum":[]},{"oauth2":["maintenance.write"]}],"parameters":[{"name":"device","in":"path","required":true,"description":"Device ULID.","schema":{"type":"string"}},{"name":"document","in":"path","required":true,"description":"Document ID.","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application\/json":{"schema":{"type":"object","properties":{"name":{"type":"string"},"expires_at":{"type":["string","null"],"format":"date"}},"required":["name"],"additionalProperties":false}}}},"responses":{"200":{"description":"Updated document.","content":{"application\/json":{"schema":{"type":"object","properties":{"data":{"$ref":"#\/components\/schemas\/DeviceDocument"}},"additionalProperties":false}}}},"401":{"$ref":"#\/components\/responses\/Unauthorized"},"403":{"$ref":"#\/components\/responses\/Forbidden"},"404":{"$ref":"#\/components\/responses\/NotFound"},"422":{"$ref":"#\/components\/responses\/ValidationError"}}},"delete":{"operationId":"operate.device-document.destroy","summary":"Delete a document","tags":["Operate: Maintenance"],"security":[{"sanctum":[]},{"oauth2":["maintenance.write"]}],"parameters":[{"name":"device","in":"path","required":true,"description":"Device ULID.","schema":{"type":"string"}},{"name":"document","in":"path","required":true,"description":"Document ID.","schema":{"type":"string"}}],"responses":{"204":{"description":"Deleted."},"401":{"$ref":"#\/components\/responses\/Unauthorized"},"403":{"$ref":"#\/components\/responses\/Forbidden"},"404":{"$ref":"#\/components\/responses\/NotFound"}}}},"\/api\/device\/{device}\/issue":{"get":{"operationId":"operate.device-issue.index","summary":"List a device's flagged issues","tags":["Operate: Maintenance"],"security":[{"sanctum":[]},{"oauth2":["maintenance.read"]}],"parameters":[{"name":"device","in":"path","required":true,"description":"Device ULID.","schema":{"type":"string"}},{"name":"page","in":"query","required":false,"description":"Page number (1-indexed).","schema":{"type":"integer"}},{"name":"per_page","in":"query","required":false,"description":"Page size.","schema":{"type":"integer"}}],"responses":{"200":{"description":"Paginated issue list.","content":{"application\/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#\/components\/schemas\/DeviceIssue"}},"links":{"$ref":"#\/components\/schemas\/PaginationLinks"},"meta":{"$ref":"#\/components\/schemas\/PaginationMeta"}},"additionalProperties":false}}}},"401":{"$ref":"#\/components\/responses\/Unauthorized"},"403":{"$ref":"#\/components\/responses\/Forbidden"},"404":{"$ref":"#\/components\/responses\/NotFound"}}},"post":{"operationId":"operate.device-issue.store","summary":"Flag an issue on a device","tags":["Operate: Maintenance"],"security":[{"sanctum":[]},{"oauth2":["maintenance.write"]}],"parameters":[{"name":"device","in":"path","required":true,"description":"Device ULID.","schema":{"type":"string"}}],"requestBody":{"required":false,"content":{"application\/json":{"schema":{"type":"object","description":"No fields are currently validated server-side (empty rule set) \u2014 body accepted but effectively unused.","additionalProperties":true}}}},"responses":{"200":{"description":"Created issue.","content":{"application\/json":{"schema":{"type":"object","properties":{"data":{"$ref":"#\/components\/schemas\/DeviceIssue"}},"additionalProperties":false}}}},"401":{"$ref":"#\/components\/responses\/Unauthorized"},"403":{"$ref":"#\/components\/responses\/Forbidden"},"404":{"$ref":"#\/components\/responses\/NotFound"},"422":{"$ref":"#\/components\/responses\/ValidationError"}}}},"\/api\/device\/{device}\/issue\/{issue}":{"delete":{"operationId":"operate.device-issue.destroy","summary":"Delete an issue","tags":["Operate: Maintenance"],"security":[{"sanctum":[]},{"oauth2":["maintenance.write"]}],"parameters":[{"name":"device","in":"path","required":true,"description":"Device ULID.","schema":{"type":"string"}},{"name":"issue","in":"path","required":true,"description":"Issue ID.","schema":{"type":"string"}}],"responses":{"204":{"description":"Deleted."},"401":{"$ref":"#\/components\/responses\/Unauthorized"},"403":{"$ref":"#\/components\/responses\/Forbidden"},"404":{"$ref":"#\/components\/responses\/NotFound"}}}},"\/api\/device\/{device}\/service":{"get":{"operationId":"operate.device-service.index","summary":"List a device's maintenance services","tags":["Operate: Maintenance"],"security":[{"sanctum":[]},{"oauth2":["maintenance.read"]}],"parameters":[{"name":"device","in":"path","required":true,"description":"Device ULID.","schema":{"type":"string"}},{"name":"page","in":"query","required":false,"description":"Page number (1-indexed).","schema":{"type":"integer"}},{"name":"per_page","in":"query","required":false,"description":"Page size.","schema":{"type":"integer"}}],"responses":{"200":{"description":"Paginated service list.","content":{"application\/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#\/components\/schemas\/DeviceService"}},"links":{"$ref":"#\/components\/schemas\/PaginationLinks"},"meta":{"$ref":"#\/components\/schemas\/PaginationMeta"}},"additionalProperties":false}}}},"401":{"$ref":"#\/components\/responses\/Unauthorized"},"403":{"$ref":"#\/components\/responses\/Forbidden"},"404":{"$ref":"#\/components\/responses\/NotFound"}}},"post":{"operationId":"operate.device-service.store","summary":"Record a maintenance service on a device","tags":["Operate: Maintenance"],"security":[{"sanctum":[]},{"oauth2":["maintenance.write"]}],"parameters":[{"name":"device","in":"path","required":true,"description":"Device ULID.","schema":{"type":"string"}}],"requestBody":{"required":false,"content":{"application\/json":{"schema":{"type":"object","description":"Issues this service resolves are attached to the created service via the service_has_issues pivot. Only \"issues\" is validated server-side; other fields are accepted but currently unused.","properties":{"issues":{"type":"array","items":{"type":"string","description":"Issue ULID \u2014 must belong to an existing device_issues row."}}},"additionalProperties":true}}}},"responses":{"200":{"description":"Created service.","content":{"application\/json":{"schema":{"type":"object","properties":{"data":{"$ref":"#\/components\/schemas\/DeviceService"}},"additionalProperties":false}}}},"401":{"$ref":"#\/components\/responses\/Unauthorized"},"403":{"$ref":"#\/components\/responses\/Forbidden"},"404":{"$ref":"#\/components\/responses\/NotFound"},"422":{"$ref":"#\/components\/responses\/ValidationError"}}}},"\/api\/device\/{device}\/service\/{service}":{"delete":{"operationId":"operate.device-service.destroy","summary":"Delete a service","tags":["Operate: Maintenance"],"security":[{"sanctum":[]},{"oauth2":["maintenance.write"]}],"parameters":[{"name":"device","in":"path","required":true,"description":"Device ULID.","schema":{"type":"string"}},{"name":"service","in":"path","required":true,"description":"Service ID.","schema":{"type":"string"}}],"responses":{"204":{"description":"Deleted."},"401":{"$ref":"#\/components\/responses\/Unauthorized"},"403":{"$ref":"#\/components\/responses\/Forbidden"},"404":{"$ref":"#\/components\/responses\/NotFound"}}}},"\/api\/device\/{device}\/transaction":{"get":{"operationId":"operate.device-transaction.index","summary":"List a device's cost\/revenue transactions, plus revenue\/expense\/profit aggregates for the filtered window","tags":["Operate: Maintenance"],"security":[{"sanctum":[]},{"oauth2":["maintenance.read"]}],"parameters":[{"name":"device","in":"path","required":true,"description":"Device ULID.","schema":{"type":"string"}},{"name":"created_before","in":"query","required":false,"description":"ISO date-time upper bound.","schema":{"type":"string","format":"date-time"}},{"name":"created_after","in":"query","required":false,"description":"ISO date-time lower bound.","schema":{"type":"string","format":"date-time"}},{"name":"q","in":"query","required":false,"description":"Search text.","schema":{"type":"string"}},{"name":"amount_unit","in":"query","required":false,"description":"\"subunit\" returns amount\/aggregates in minor units instead of major-unit floats.","schema":{"type":"string"}},{"name":"per_page","in":"query","required":false,"description":"Page size, capped at 100 (default 30).","schema":{"type":"integer"}},{"name":"page","in":"query","required":false,"description":"Page number (1-indexed).","schema":{"type":"integer"}}],"responses":{"200":{"description":"Paginated transaction list with aggregates.","content":{"application\/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#\/components\/schemas\/Transaction"}},"links":{"$ref":"#\/components\/schemas\/PaginationLinks"},"meta":{"$ref":"#\/components\/schemas\/PaginationMeta"},"aggregates":{"type":"object","description":"mileage is in kilometers.","properties":{"revenue":{"type":"integer","description":"Sum of positive amounts, in the unit selected by amount_unit."},"expenses":{"type":"integer","description":"Sum of negative amounts, in the unit selected by amount_unit."},"profit":{"type":"integer","description":"revenue + expenses."},"mileage":{"type":"number"},"revenue_per_km":{"type":["number","null"]},"cost_per_km":{"type":["number","null"]},"profit_per_km":{"type":["number","null"]}},"required":["revenue","expenses","profit","mileage"],"additionalProperties":false}},"required":["data","aggregates"],"additionalProperties":false}}}},"401":{"$ref":"#\/components\/responses\/Unauthorized"},"403":{"$ref":"#\/components\/responses\/Forbidden"},"404":{"$ref":"#\/components\/responses\/NotFound"}}},"post":{"operationId":"operate.device-transaction.store","summary":"Record a cost or revenue transaction for a device","description":"amount is a major-unit float (e.g. dollars) by default; pass ?amount_unit=subunit on the URL to submit an integer minor-unit amount instead. Positive = revenue, negative = expense.","tags":["Operate: Maintenance"],"security":[{"sanctum":[]},{"oauth2":["maintenance.write"]}],"parameters":[{"name":"device","in":"path","required":true,"description":"Device ULID.","schema":{"type":"string"}},{"name":"amount_unit","in":"query","required":false,"description":"\"subunit\" makes amount an integer minor-unit value instead of a major-unit float.","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application\/json":{"schema":{"type":"object","properties":{"label":{"type":"string"},"description":{"type":["string","null"]},"amount":{"type":"number"},"currency":{"type":["string","null"],"description":"ISO currency code. Defaults to the app's default currency."},"created_at":{"type":["string","null"],"format":"date-time"}},"required":["label","amount"],"additionalProperties":false}}}},"responses":{"200":{"description":"Created transaction.","content":{"application\/json":{"schema":{"type":"object","properties":{"data":{"$ref":"#\/components\/schemas\/Transaction"}},"additionalProperties":false}}}},"401":{"$ref":"#\/components\/responses\/Unauthorized"},"403":{"$ref":"#\/components\/responses\/Forbidden"},"404":{"$ref":"#\/components\/responses\/NotFound"},"422":{"$ref":"#\/components\/responses\/ValidationError"}}}},"\/api\/device\/{device}\/transaction\/{transaction}":{"get":{"operationId":"operate.device-transaction.show","summary":"Get a transaction","tags":["Operate: Maintenance"],"security":[{"sanctum":[]},{"oauth2":["maintenance.read"]}],"parameters":[{"name":"device","in":"path","required":true,"description":"Device ULID.","schema":{"type":"string"}},{"name":"transaction","in":"path","required":true,"description":"Transaction ID.","schema":{"type":"string"}}],"responses":{"200":{"description":"Transaction detail.","content":{"application\/json":{"schema":{"type":"object","properties":{"data":{"$ref":"#\/components\/schemas\/Transaction"}},"additionalProperties":false}}}},"401":{"$ref":"#\/components\/responses\/Unauthorized"},"403":{"$ref":"#\/components\/responses\/Forbidden"},"404":{"$ref":"#\/components\/responses\/NotFound"}}},"put":{"operationId":"operate.device-transaction.update","summary":"Update a transaction","description":"amount is a major-unit float (e.g. dollars) by default; pass ?amount_unit=subunit on the URL to submit an integer minor-unit amount instead. Positive = revenue, negative = expense.","tags":["Operate: Maintenance"],"security":[{"sanctum":[]},{"oauth2":["maintenance.write"]}],"parameters":[{"name":"device","in":"path","required":true,"description":"Device ULID.","schema":{"type":"string"}},{"name":"transaction","in":"path","required":true,"description":"Transaction ID.","schema":{"type":"string"}},{"name":"amount_unit","in":"query","required":false,"description":"\"subunit\" makes amount an integer minor-unit value instead of a major-unit float.","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application\/json":{"schema":{"type":"object","properties":{"label":{"type":"string"},"description":{"type":["string","null"]},"amount":{"type":"number"},"currency":{"type":["string","null"],"description":"ISO currency code. Defaults to the app's default currency."},"created_at":{"type":["string","null"],"format":"date-time"}},"required":["label","amount"],"additionalProperties":false}}}},"responses":{"200":{"description":"Updated transaction.","content":{"application\/json":{"schema":{"type":"object","properties":{"data":{"$ref":"#\/components\/schemas\/Transaction"}},"additionalProperties":false}}}},"401":{"$ref":"#\/components\/responses\/Unauthorized"},"403":{"$ref":"#\/components\/responses\/Forbidden"},"404":{"$ref":"#\/components\/responses\/NotFound"},"422":{"$ref":"#\/components\/responses\/ValidationError"}}},"delete":{"operationId":"operate.device-transaction.destroy","summary":"Delete a transaction","tags":["Operate: Maintenance"],"security":[{"sanctum":[]},{"oauth2":["maintenance.write"]}],"parameters":[{"name":"device","in":"path","required":true,"description":"Device ULID.","schema":{"type":"string"}},{"name":"transaction","in":"path","required":true,"description":"Transaction ID.","schema":{"type":"string"}}],"responses":{"204":{"description":"Deleted."},"401":{"$ref":"#\/components\/responses\/Unauthorized"},"403":{"$ref":"#\/components\/responses\/Forbidden"},"404":{"$ref":"#\/components\/responses\/NotFound"}}}},"\/api\/device\/{device}\/settings\/speed-limit":{"get":{"operationId":"operate.device-settings-speed-limit.show","summary":"Get the caller's Speed limit alert threshold setting for this device","description":"404 when the caller has not configured this setting on this device.","tags":["Operate: Settings"],"security":[{"sanctum":[]},{"oauth2":["device.settings.read"]}],"parameters":[{"name":"device","in":"path","required":true,"description":"Device ULID.","schema":{"type":"string"}}],"responses":{"200":{"description":"Setting detail.","content":{"application\/json":{"schema":{"type":"object","properties":{"data":{"type":"object","properties":{"id":{"type":"string","description":"ULID."},"type":{"type":"string","description":"SettingsTypeEnum value, \"speed_limit\" for this kind."},"data":{"type":"object","properties":{"max_speed":{"type":"number"}},"required":["max_speed"],"additionalProperties":false}},"required":["id","type","data"],"additionalProperties":false}},"additionalProperties":false}}}},"401":{"$ref":"#\/components\/responses\/Unauthorized"},"403":{"$ref":"#\/components\/responses\/Forbidden"},"404":{"$ref":"#\/components\/responses\/NotFound"}}},"post":{"operationId":"operate.device-settings-speed-limit.store","summary":"Create or update the caller's Speed limit alert threshold setting for this device","description":"max_speed is in km\/h.","tags":["Operate: Settings"],"security":[{"sanctum":[]},{"oauth2":["device.settings.write"]}],"parameters":[{"name":"device","in":"path","required":true,"description":"Device ULID.","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application\/json":{"schema":{"type":"object","properties":{"max_speed":{"type":"number"}},"required":["max_speed"],"additionalProperties":false}}}},"responses":{"200":{"description":"Upserted. This is a 200 with an empty body \u2014 not the Speed limit alert threshold envelope."},"401":{"$ref":"#\/components\/responses\/Unauthorized"},"403":{"$ref":"#\/components\/responses\/Forbidden"},"404":{"$ref":"#\/components\/responses\/NotFound"},"422":{"$ref":"#\/components\/responses\/ValidationError"}}},"delete":{"operationId":"operate.device-settings-speed-limit.destroy","summary":"Remove the caller's Speed limit alert threshold setting for this device","tags":["Operate: Settings"],"security":[{"sanctum":[]},{"oauth2":["device.settings.write"]}],"parameters":[{"name":"device","in":"path","required":true,"description":"Device ULID.","schema":{"type":"string"}}],"responses":{"204":{"description":"Deleted."},"401":{"$ref":"#\/components\/responses\/Unauthorized"},"403":{"$ref":"#\/components\/responses\/Forbidden"},"404":{"$ref":"#\/components\/responses\/NotFound"}}}},"\/api\/device\/{device}\/settings\/towing":{"get":{"operationId":"operate.device-settings-towing.show","summary":"Get the caller's Unauthorized-towing detection thresholds setting for this device","description":"404 when the caller has not configured this setting on this device.","tags":["Operate: Settings"],"security":[{"sanctum":[]},{"oauth2":["device.settings.read"]}],"parameters":[{"name":"device","in":"path","required":true,"description":"Device ULID.","schema":{"type":"string"}}],"responses":{"200":{"description":"Setting detail.","content":{"application\/json":{"schema":{"type":"object","properties":{"data":{"type":"object","properties":{"id":{"type":"string","description":"ULID."},"type":{"type":"string","description":"SettingsTypeEnum value, \"towing\" for this kind."},"data":{"type":"object","properties":{"min_distance":{"type":"number"},"min_speed":{"type":["number","null"]}},"required":["min_distance"],"additionalProperties":false}},"required":["id","type","data"],"additionalProperties":false}},"additionalProperties":false}}}},"401":{"$ref":"#\/components\/responses\/Unauthorized"},"403":{"$ref":"#\/components\/responses\/Forbidden"},"404":{"$ref":"#\/components\/responses\/NotFound"}}},"post":{"operationId":"operate.device-settings-towing.store","summary":"Create or update the caller's Unauthorized-towing detection thresholds setting for this device","description":"min_distance is in meters (50-5000); min_speed is in km\/h (3-50).","tags":["Operate: Settings"],"security":[{"sanctum":[]},{"oauth2":["device.settings.write"]}],"parameters":[{"name":"device","in":"path","required":true,"description":"Device ULID.","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application\/json":{"schema":{"type":"object","properties":{"min_distance":{"type":"number"},"min_speed":{"type":["number","null"]}},"required":["min_distance"],"additionalProperties":false}}}},"responses":{"200":{"description":"Upserted. This is a 200 with an empty body \u2014 not the Unauthorized-towing detection thresholds envelope."},"401":{"$ref":"#\/components\/responses\/Unauthorized"},"403":{"$ref":"#\/components\/responses\/Forbidden"},"404":{"$ref":"#\/components\/responses\/NotFound"},"422":{"$ref":"#\/components\/responses\/ValidationError"}}},"delete":{"operationId":"operate.device-settings-towing.destroy","summary":"Remove the caller's Unauthorized-towing detection thresholds setting for this device","tags":["Operate: Settings"],"security":[{"sanctum":[]},{"oauth2":["device.settings.write"]}],"parameters":[{"name":"device","in":"path","required":true,"description":"Device ULID.","schema":{"type":"string"}}],"responses":{"204":{"description":"Deleted."},"401":{"$ref":"#\/components\/responses\/Unauthorized"},"403":{"$ref":"#\/components\/responses\/Forbidden"},"404":{"$ref":"#\/components\/responses\/NotFound"}}}},"\/api\/device\/{device}\/settings\/low-battery":{"get":{"operationId":"operate.device-settings-low-battery.show","summary":"Get the caller's Low internal-battery alert setting for this device","description":"404 when the caller has not configured this setting on this device.","tags":["Operate: Settings"],"security":[{"sanctum":[]},{"oauth2":["device.settings.read"]}],"parameters":[{"name":"device","in":"path","required":true,"description":"Device ULID.","schema":{"type":"string"}}],"responses":{"200":{"description":"Setting detail.","content":{"application\/json":{"schema":{"type":"object","properties":{"data":{"type":"object","properties":{"id":{"type":"string","description":"ULID."},"type":{"type":"string","description":"SettingsTypeEnum value, \"low_battery\" for this kind."},"data":{"type":"object","description":"No configurable fields \u2014 presence of the settings row is itself the toggle.","additionalProperties":true}},"required":["id","type","data"],"additionalProperties":false}},"additionalProperties":false}}}},"401":{"$ref":"#\/components\/responses\/Unauthorized"},"403":{"$ref":"#\/components\/responses\/Forbidden"},"404":{"$ref":"#\/components\/responses\/NotFound"}}},"post":{"operationId":"operate.device-settings-low-battery.store","summary":"Create or update the caller's Low internal-battery alert setting for this device","tags":["Operate: Settings"],"security":[{"sanctum":[]},{"oauth2":["device.settings.write"]}],"parameters":[{"name":"device","in":"path","required":true,"description":"Device ULID.","schema":{"type":"string"}}],"requestBody":{"required":false,"content":{"application\/json":{"schema":{"type":"object","additionalProperties":true}}}},"responses":{"200":{"description":"Upserted. This is a 200 with an empty body \u2014 not the Low internal-battery alert envelope."},"401":{"$ref":"#\/components\/responses\/Unauthorized"},"403":{"$ref":"#\/components\/responses\/Forbidden"},"404":{"$ref":"#\/components\/responses\/NotFound"},"422":{"$ref":"#\/components\/responses\/ValidationError"}}},"delete":{"operationId":"operate.device-settings-low-battery.destroy","summary":"Remove the caller's Low internal-battery alert setting for this device","tags":["Operate: Settings"],"security":[{"sanctum":[]},{"oauth2":["device.settings.write"]}],"parameters":[{"name":"device","in":"path","required":true,"description":"Device ULID.","schema":{"type":"string"}}],"responses":{"204":{"description":"Deleted."},"401":{"$ref":"#\/components\/responses\/Unauthorized"},"403":{"$ref":"#\/components\/responses\/Forbidden"},"404":{"$ref":"#\/components\/responses\/NotFound"}}}},"\/api\/device\/{device}\/settings\/impact-detection":{"get":{"operationId":"operate.device-settings-impact-detection.show","summary":"Get the caller's Impact\/collision detection alert setting for this device","description":"404 when the caller has not configured this setting on this device.","tags":["Operate: Settings"],"security":[{"sanctum":[]},{"oauth2":["device.settings.read"]}],"parameters":[{"name":"device","in":"path","required":true,"description":"Device ULID.","schema":{"type":"string"}}],"responses":{"200":{"description":"Setting detail.","content":{"application\/json":{"schema":{"type":"object","properties":{"data":{"type":"object","properties":{"id":{"type":"string","description":"ULID."},"type":{"type":"string","description":"SettingsTypeEnum value, \"impact_detection\" for this kind."},"data":{"type":"object","description":"No configurable fields \u2014 presence of the settings row is itself the toggle.","additionalProperties":true}},"required":["id","type","data"],"additionalProperties":false}},"additionalProperties":false}}}},"401":{"$ref":"#\/components\/responses\/Unauthorized"},"403":{"$ref":"#\/components\/responses\/Forbidden"},"404":{"$ref":"#\/components\/responses\/NotFound"}}},"post":{"operationId":"operate.device-settings-impact-detection.store","summary":"Create or update the caller's Impact\/collision detection alert setting for this device","tags":["Operate: Settings"],"security":[{"sanctum":[]},{"oauth2":["device.settings.write"]}],"parameters":[{"name":"device","in":"path","required":true,"description":"Device ULID.","schema":{"type":"string"}}],"requestBody":{"required":false,"content":{"application\/json":{"schema":{"type":"object","additionalProperties":true}}}},"responses":{"200":{"description":"Upserted. This is a 200 with an empty body \u2014 not the Impact\/collision detection alert envelope."},"401":{"$ref":"#\/components\/responses\/Unauthorized"},"403":{"$ref":"#\/components\/responses\/Forbidden"},"404":{"$ref":"#\/components\/responses\/NotFound"},"422":{"$ref":"#\/components\/responses\/ValidationError"}}},"delete":{"operationId":"operate.device-settings-impact-detection.destroy","summary":"Remove the caller's Impact\/collision detection alert setting for this device","tags":["Operate: Settings"],"security":[{"sanctum":[]},{"oauth2":["device.settings.write"]}],"parameters":[{"name":"device","in":"path","required":true,"description":"Device ULID.","schema":{"type":"string"}}],"responses":{"204":{"description":"Deleted."},"401":{"$ref":"#\/components\/responses\/Unauthorized"},"403":{"$ref":"#\/components\/responses\/Forbidden"},"404":{"$ref":"#\/components\/responses\/NotFound"}}}},"\/api\/device\/{device}\/settings\/fuel-monitoring":{"get":{"operationId":"operate.device-settings-fuel-monitoring.show","summary":"Get the caller's Fuel refill\/leak\/theft monitoring thresholds setting for this device","description":"404 when the caller has not configured this setting on this device.","tags":["Operate: Settings"],"security":[{"sanctum":[]},{"oauth2":["device.settings.read"]}],"parameters":[{"name":"device","in":"path","required":true,"description":"Device ULID.","schema":{"type":"string"}}],"responses":{"200":{"description":"Setting detail.","content":{"application\/json":{"schema":{"type":"object","properties":{"data":{"type":"object","properties":{"id":{"type":"string","description":"ULID."},"type":{"type":"string","description":"SettingsTypeEnum value, \"fuel_monitoring\" for this kind."},"data":{"type":"object","additionalProperties":true}},"required":["id","type","data"],"additionalProperties":false}},"additionalProperties":false}}}},"401":{"$ref":"#\/components\/responses\/Unauthorized"},"403":{"$ref":"#\/components\/responses\/Forbidden"},"404":{"$ref":"#\/components\/responses\/NotFound"}}},"post":{"operationId":"operate.device-settings-fuel-monitoring.store","summary":"Create or update the caller's Fuel refill\/leak\/theft monitoring thresholds setting for this device","description":"Threshold fields are in liters (fuel_refill\/leak\/theft_threshold >= 0.1, fuel_min_precision >= 0). 422 if the device has no fuel sensor (attributes.can_see_fuel_level is falsy).","tags":["Operate: Settings"],"security":[{"sanctum":[]},{"oauth2":["device.settings.write"]}],"parameters":[{"name":"device","in":"path","required":true,"description":"Device ULID.","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application\/json":{"schema":{"type":"object","properties":{"notify_fuel_refill":{"type":"boolean"},"notify_fuel_leak":{"type":"boolean"},"fuel_refill_threshold":{"type":["number","null"]},"fuel_leak_threshold":{"type":["number","null"]},"fuel_theft_threshold":{"type":["number","null"]},"fuel_min_precision":{"type":["number","null"]}},"required":["notify_fuel_refill","notify_fuel_leak"],"additionalProperties":false}}}},"responses":{"200":{"description":"Upserted. This is a 200 with an empty body \u2014 not the Fuel refill\/leak\/theft monitoring thresholds envelope."},"401":{"$ref":"#\/components\/responses\/Unauthorized"},"403":{"$ref":"#\/components\/responses\/Forbidden"},"404":{"$ref":"#\/components\/responses\/NotFound"},"422":{"$ref":"#\/components\/responses\/ValidationError"}}},"delete":{"operationId":"operate.device-settings-fuel-monitoring.destroy","summary":"Remove the caller's Fuel refill\/leak\/theft monitoring thresholds setting for this device","tags":["Operate: Settings"],"security":[{"sanctum":[]},{"oauth2":["device.settings.write"]}],"parameters":[{"name":"device","in":"path","required":true,"description":"Device ULID.","schema":{"type":"string"}}],"responses":{"204":{"description":"Deleted."},"401":{"$ref":"#\/components\/responses\/Unauthorized"},"403":{"$ref":"#\/components\/responses\/Forbidden"},"404":{"$ref":"#\/components\/responses\/NotFound"}}}},"\/api\/device\/{device}\/settings\/geo-zone":{"get":{"operationId":"operate.device-settings-geo-zone.show","summary":"Get the caller's per-geofence enter\/exit alert toggles for this device","description":"404 when the caller has not configured this setting on this device.","tags":["Operate: Settings"],"security":[{"sanctum":[]},{"oauth2":["device.settings.read"]}],"parameters":[{"name":"device","in":"path","required":true,"description":"Device ULID.","schema":{"type":"string"}}],"responses":{"200":{"description":"Setting detail.","content":{"application\/json":{"schema":{"type":"object","properties":{"data":{"type":"object","properties":{"id":{"type":"string","description":"ULID."},"type":{"type":"string","description":"SettingsTypeEnum value, \"geofence\" for this kind."},"data":{"type":["object","null"],"description":"Map of geofence ID to {enter: bool, exit: bool} alert toggles.","additionalProperties":true}},"required":["id","type"],"additionalProperties":false}},"additionalProperties":false}}}},"401":{"$ref":"#\/components\/responses\/Unauthorized"},"403":{"$ref":"#\/components\/responses\/Forbidden"},"404":{"$ref":"#\/components\/responses\/NotFound"}}},"post":{"operationId":"operate.device-settings-geo-zone.store","summary":"Set the caller's per-geofence enter\/exit alert toggles for this device","tags":["Operate: Settings"],"security":[{"sanctum":[]},{"oauth2":["device.settings.write"]}],"parameters":[{"name":"device","in":"path","required":true,"description":"Device ULID.","schema":{"type":"string"}}],"requestBody":{"required":false,"content":{"application\/json":{"schema":{"type":"object","properties":{"data":{"type":["object","null"],"description":"Map of geofence ID to {enter: bool, exit: bool}. Not validated server-side beyond \"nullable\".","additionalProperties":true}},"additionalProperties":false}}}},"responses":{"200":{"description":"Upserted setting (unlike the other settings kinds, this one returns a body).","content":{"application\/json":{"schema":{"type":"object","properties":{"data":{"type":"object","properties":{"id":{"type":"string","description":"ULID."},"type":{"type":"string","description":"SettingsTypeEnum value, \"geofence\" for this kind."},"data":{"type":["object","null"],"description":"Map of geofence ID to {enter: bool, exit: bool} alert toggles.","additionalProperties":true}},"required":["id","type"],"additionalProperties":false}},"additionalProperties":false}}}},"401":{"$ref":"#\/components\/responses\/Unauthorized"},"403":{"$ref":"#\/components\/responses\/Forbidden"},"404":{"$ref":"#\/components\/responses\/NotFound"},"422":{"$ref":"#\/components\/responses\/ValidationError"}}},"delete":{"operationId":"operate.device-settings-geo-zone.destroy","summary":"Remove the caller's per-geofence alert toggles for this device","tags":["Operate: Settings"],"security":[{"sanctum":[]},{"oauth2":["device.settings.write"]}],"parameters":[{"name":"device","in":"path","required":true,"description":"Device ULID.","schema":{"type":"string"}}],"responses":{"204":{"description":"Deleted."},"401":{"$ref":"#\/components\/responses\/Unauthorized"},"403":{"$ref":"#\/components\/responses\/Forbidden"},"404":{"$ref":"#\/components\/responses\/NotFound"}}}},"\/api\/user\/me":{"get":{"operationId":"manage-account.user-me.show","summary":"Get the authenticated caller's own profile","description":"Always eager-loads subscriptions and feature_flags. Response additionally includes a top-level \"app\" object (support_phone, support_email, min_version, latest_version) alongside \"data\".","tags":["Account"],"security":[{"sanctum":[]},{"oauth2":["user.view"]}],"parameters":[],"responses":{"200":{"description":"Caller's profile.","content":{"application\/json":{"schema":{"type":"object","properties":{"data":{"$ref":"#\/components\/schemas\/User"}},"additionalProperties":false},"examples":{"default":{"$ref":"#\/components\/examples\/UserMeResponse"}}}}},"401":{"$ref":"#\/components\/responses\/Unauthorized"},"403":{"$ref":"#\/components\/responses\/Forbidden"}}}},"\/api\/user\/{user}":{"put":{"operationId":"manage-account.user.update","summary":"Update the caller's name, phone, and email","tags":["Account"],"security":[{"sanctum":[]},{"oauth2":["user.update"]}],"parameters":[{"name":"user","in":"path","required":true,"description":"User ULID; must be the authenticated caller (policy-enforced).","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application\/json":{"schema":{"type":"object","properties":{"name":{"type":"string"},"phone":{"type":"string","description":"Must be unique across users (ignoring this user's own current value); normalized server-side."},"email":{"type":"string","description":"Must be unique across users (ignoring this user's own current value)."}},"required":["name","phone","email"],"additionalProperties":false}}}},"responses":{"200":{"description":"Updated profile.","content":{"application\/json":{"schema":{"type":"object","properties":{"data":{"$ref":"#\/components\/schemas\/User"}},"additionalProperties":false}}}},"401":{"$ref":"#\/components\/responses\/Unauthorized"},"403":{"$ref":"#\/components\/responses\/Forbidden"},"404":{"$ref":"#\/components\/responses\/NotFound"},"422":{"$ref":"#\/components\/responses\/ValidationError"}}}},"\/api\/auth\/sessions":{"get":{"operationId":"manage-account.auth-sessions.index","summary":"List the caller's active API sessions (Sanctum personal access tokens)","tags":["Account"],"security":[{"sanctum":[]},{"oauth2":["user.view"]}],"parameters":[],"responses":{"200":{"description":"Session list.","content":{"application\/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","description":"Sanctum personal access token ID, as a string."},"type":{"type":"string","description":"Always \"api\"."},"browser":{"type":["string","null"],"description":"Always null \u2014 not parsed for API tokens."},"os":{"type":["string","null"]},"device_type":{"type":["string","null"]},"device_brand":{"type":["string","null"]},"is_mobile":{"type":"boolean"},"is_tablet":{"type":"boolean"},"ip":{"type":["string","null"],"description":"Sensitive identifier."},"city":{"type":["string","null"]},"country":{"type":["string","null"]},"is_current":{"type":"boolean"},"created_at":{"type":"string","format":"date-time"},"last_used_at":{"type":["string","null"],"format":"date-time"}},"required":["id","type","is_current"],"additionalProperties":false}}},"additionalProperties":false}}}},"401":{"$ref":"#\/components\/responses\/Unauthorized"},"403":{"$ref":"#\/components\/responses\/Forbidden"}}}},"\/api\/auth\/sessions\/{session}":{"delete":{"operationId":"manage-account.auth-sessions.destroy","summary":"Revoke another session (sign that device out)","description":"422 when the caller targets their own current session \u2014 use logout instead. 404 when the session doesn't belong to the caller.","tags":["Account"],"security":[{"sanctum":[]},{"oauth2":["session.revoke"]}],"parameters":[{"name":"session","in":"path","required":true,"description":"Sanctum personal access token ID.","schema":{"type":"string"}}],"responses":{"200":{"description":"Confirmation message.","content":{"application\/json":{"schema":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}}},"401":{"$ref":"#\/components\/responses\/Unauthorized"},"403":{"$ref":"#\/components\/responses\/Forbidden"},"404":{"$ref":"#\/components\/responses\/NotFound"}}}},"\/api\/user\/{user}\/settings\/currency":{"post":{"operationId":"manage-account.user-settings-currency.store","summary":"Set the caller's preferred display currency","tags":["Account"],"security":[{"sanctum":[]},{"oauth2":["user.update"]}],"parameters":[{"name":"user","in":"path","required":true,"description":"User ULID; must be the authenticated caller (policy-enforced).","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application\/json":{"schema":{"type":"object","properties":{"currency":{"type":"string","description":"ISO currency code."}},"required":["currency"],"additionalProperties":false}}}},"responses":{"200":{"description":"Updated profile.","content":{"application\/json":{"schema":{"type":"object","properties":{"data":{"$ref":"#\/components\/schemas\/User"}},"additionalProperties":false}}}},"401":{"$ref":"#\/components\/responses\/Unauthorized"},"403":{"$ref":"#\/components\/responses\/Forbidden"},"404":{"$ref":"#\/components\/responses\/NotFound"},"422":{"$ref":"#\/components\/responses\/ValidationError"}}}},"\/api\/user\/{user}\/settings\/locale":{"post":{"operationId":"manage-account.user-settings-locale.store","summary":"Set the caller's preferred locale","tags":["Account"],"security":[{"sanctum":[]},{"oauth2":["user.update"]}],"parameters":[{"name":"user","in":"path","required":true,"description":"User ULID; must be the authenticated caller (policy-enforced).","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application\/json":{"schema":{"type":"object","properties":{"locale":{"type":"string","description":"Locale enum value (LocaleEnum)."}},"required":["locale"],"additionalProperties":false}}}},"responses":{"200":{"description":"Updated profile.","content":{"application\/json":{"schema":{"type":"object","properties":{"data":{"$ref":"#\/components\/schemas\/User"}},"additionalProperties":false}}}},"401":{"$ref":"#\/components\/responses\/Unauthorized"},"403":{"$ref":"#\/components\/responses\/Forbidden"},"404":{"$ref":"#\/components\/responses\/NotFound"},"422":{"$ref":"#\/components\/responses\/ValidationError"}}}},"\/api\/subscription":{"get":{"operationId":"manage-account.subscription.index","summary":"List all subscriptions (admin-scoped; requires subscription.view_any)","tags":["Subscriptions"],"security":[{"sanctum":[]},{"oauth2":["subscription.view"]}],"parameters":[{"name":"page","in":"query","required":false,"description":"Page number (1-indexed).","schema":{"type":"integer"}},{"name":"per_page","in":"query","required":false,"description":"Page size.","schema":{"type":"integer"}}],"responses":{"200":{"description":"Paginated subscription list.","content":{"application\/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#\/components\/schemas\/Subscription"}},"links":{"$ref":"#\/components\/schemas\/PaginationLinks"},"meta":{"$ref":"#\/components\/schemas\/PaginationMeta"}},"additionalProperties":false}}}},"401":{"$ref":"#\/components\/responses\/Unauthorized"},"403":{"$ref":"#\/components\/responses\/Forbidden"}}}},"\/api\/subscription\/{subscription}":{"get":{"operationId":"manage-account.subscription.show","summary":"Get a subscription","tags":["Subscriptions"],"security":[{"sanctum":[]},{"oauth2":["subscription.view"]}],"parameters":[{"name":"subscription","in":"path","required":true,"description":"Subscription ULID.","schema":{"type":"string"}}],"responses":{"200":{"description":"Subscription detail.","content":{"application\/json":{"schema":{"type":"object","properties":{"data":{"$ref":"#\/components\/schemas\/Subscription"}},"additionalProperties":false}}}},"401":{"$ref":"#\/components\/responses\/Unauthorized"},"403":{"$ref":"#\/components\/responses\/Forbidden"},"404":{"$ref":"#\/components\/responses\/NotFound"}}},"put":{"operationId":"manage-account.subscription.update","summary":"Renew, upgrade, or downgrade a subscription","description":"Creates an order + pending payment and returns a gateway checkout URL for renewal\/upgrade. A downgrade (new plan ranks below the current plan) is applied immediately in place and returns 204 with no payment. Requires Accept: application\/json; other callers receive a 302 redirect to the gateway checkout URL instead of a JSON body.","tags":["Subscriptions"],"security":[{"sanctum":[]},{"oauth2":["device.subscription.update"]}],"parameters":[{"name":"subscription","in":"path","required":true,"description":"Subscription ULID.","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application\/json":{"schema":{"type":"object","properties":{"plan":{"type":["string","null"],"description":"PlanEnum value. Only the subscriber may change plans \u2014 a non-subscriber caller gets a 422."},"payment_method":{"type":"string","description":"PaymentMethodEnum value."},"billing_period":{"type":["string","null"],"description":"Legacy alias for duration (PlanDurationEnum)."},"duration":{"type":["string","null"],"description":"PlanDurationEnum value. Defaults to one year."},"success_url":{"type":["string","null"]}},"required":["payment_method"],"additionalProperties":false}}}},"responses":{"200":{"description":"Created order + pending payment.","content":{"application\/json":{"schema":{"type":"object","properties":{"data":{"$ref":"#\/components\/schemas\/CheckoutResult"}},"additionalProperties":false}}}},"204":{"description":"Downgrade applied immediately; no payment created."},"401":{"$ref":"#\/components\/responses\/Unauthorized"},"403":{"$ref":"#\/components\/responses\/Forbidden"},"404":{"$ref":"#\/components\/responses\/NotFound"},"422":{"$ref":"#\/components\/responses\/ValidationError"}}}},"\/api\/subscription\/{subscription}\/renew":{"post":{"operationId":"manage-account.subscription-renew.store","summary":"Renew a subscription (backward-compat alias for PUT \/api\/subscription\/{subscription})","description":"Kept for mobile app <= 0.74.1; routes to the exact same handler as PUT \/api\/subscription\/{subscription}. Requires Accept: application\/json; other callers receive a 302 redirect to the gateway checkout URL instead of a JSON body.","tags":["Subscriptions"],"security":[{"sanctum":[]},{"oauth2":["device.subscription.update"]}],"parameters":[{"name":"subscription","in":"path","required":true,"description":"Subscription ULID.","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application\/json":{"schema":{"type":"object","properties":{"payment_method":{"type":"string","description":"PaymentMethodEnum value."},"duration":{"type":["string","null"],"description":"PlanDurationEnum value. Defaults to one year."},"success_url":{"type":["string","null"]}},"required":["payment_method"],"additionalProperties":false}}}},"responses":{"200":{"description":"Created order + pending payment.","content":{"application\/json":{"schema":{"type":"object","properties":{"data":{"$ref":"#\/components\/schemas\/CheckoutResult"}},"additionalProperties":false}}}},"204":{"description":"Downgrade applied immediately; no payment created."},"401":{"$ref":"#\/components\/responses\/Unauthorized"},"403":{"$ref":"#\/components\/responses\/Forbidden"},"404":{"$ref":"#\/components\/responses\/NotFound"},"422":{"$ref":"#\/components\/responses\/ValidationError"}}}},"\/api\/subscription\/renewals":{"post":{"operationId":"manage-account.subscription-renewals.store","summary":"Renew multiple subscriptions in a single checkout","description":"All subscriptions in the batch must resolve to the same currency, or the request 422s. Reuses (or creates) a single unpaid subscription-renewal order for the caller.","tags":["Subscriptions"],"security":[{"sanctum":[]},{"oauth2":["device.subscription.update"]}],"parameters":[],"requestBody":{"required":true,"content":{"application\/json":{"schema":{"type":"object","properties":{"items":{"type":"array","items":{"type":"object","properties":{"subscription_id":{"type":"string","description":"Subscription ULID; must belong to the caller \u2014 a mismatched or missing ID 422s the whole request."},"duration":{"type":"string","description":"PlanDurationEnum value."}},"required":["subscription_id","duration"],"additionalProperties":false}},"payment_method":{"type":"string","description":"PaymentMethodEnum value."}},"required":["items","payment_method"],"additionalProperties":false}}}},"responses":{"200":{"description":"Created\/updated order + pending payment.","content":{"application\/json":{"schema":{"type":"object","properties":{"checkout_url":{"type":"string","format":"uri"},"payment_id":{"type":"string"},"order_id":{"type":"string"}},"required":["checkout_url","payment_id","order_id"],"additionalProperties":false}}}},"401":{"$ref":"#\/components\/responses\/Unauthorized"},"403":{"$ref":"#\/components\/responses\/Forbidden"},"422":{"$ref":"#\/components\/responses\/ValidationError"}}}},"\/api\/subscription\/{subscription}\/prices":{"get":{"operationId":"manage-account.subscription-prices.index","summary":"Quote renewal prices for a subscription across every plan duration","tags":["Subscriptions"],"security":[{"sanctum":[]},{"oauth2":["subscription.view"]}],"parameters":[{"name":"subscription","in":"path","required":true,"description":"Subscription ULID.","schema":{"type":"string"}}],"responses":{"200":{"description":"Duration-keyed price quotes.","content":{"application\/json":{"schema":{"type":"object","properties":{"data":{"type":"object","description":"Keyed by PlanDurationEnum value (e.g. \"one_year\").","additionalProperties":{"type":"object","properties":{"label":{"type":"string"},"value":{"type":"string","description":"PlanDurationEnum value."},"price":{"$ref":"#\/components\/schemas\/MoneyAmountMinor"},"price_text":{"type":"string"},"saving_percentage":{"type":["integer","null"]},"currency":{"type":["string","null"]},"roaming_cost":{"type":["number","null"]},"roaming_countries":{"type":"array","items":{"type":"string"}}},"additionalProperties":false}}},"additionalProperties":false}}}},"401":{"$ref":"#\/components\/responses\/Unauthorized"},"403":{"$ref":"#\/components\/responses\/Forbidden"},"404":{"$ref":"#\/components\/responses\/NotFound"}}}},"\/api\/user\/{user}\/subscription":{"get":{"operationId":"manage-account.user-subscription.index","summary":"List a user's subscriptions (own + subscriptions on devices they own)","tags":["Subscriptions"],"security":[{"sanctum":[]},{"oauth2":["subscription.view"]}],"parameters":[{"name":"user","in":"path","required":true,"description":"User ULID.","schema":{"type":"string"}},{"name":"expiring","in":"query","required":false,"description":"Filter to subscriptions ending within 3 days.","schema":{"type":"boolean"}},{"name":"page","in":"query","required":false,"description":"Page number (1-indexed).","schema":{"type":"integer"}},{"name":"per_page","in":"query","required":false,"description":"Page size.","schema":{"type":"integer"}}],"responses":{"200":{"description":"Paginated subscription list.","content":{"application\/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#\/components\/schemas\/Subscription"}},"links":{"$ref":"#\/components\/schemas\/PaginationLinks"},"meta":{"$ref":"#\/components\/schemas\/PaginationMeta"}},"additionalProperties":false}}}},"401":{"$ref":"#\/components\/responses\/Unauthorized"},"403":{"$ref":"#\/components\/responses\/Forbidden"},"404":{"$ref":"#\/components\/responses\/NotFound"}}}},"\/api\/user\/{user}\/subscription\/{subscription}":{"get":{"operationId":"manage-account.user-subscription.show","summary":"Get a subscription scoped to a user","tags":["Subscriptions"],"security":[{"sanctum":[]},{"oauth2":["subscription.view"]}],"parameters":[{"name":"user","in":"path","required":true,"description":"User ULID.","schema":{"type":"string"}},{"name":"subscription","in":"path","required":true,"description":"Subscription ID.","schema":{"type":"string"}}],"responses":{"200":{"description":"Subscription detail.","content":{"application\/json":{"schema":{"type":"object","properties":{"data":{"$ref":"#\/components\/schemas\/Subscription"}},"additionalProperties":false}}}},"401":{"$ref":"#\/components\/responses\/Unauthorized"},"403":{"$ref":"#\/components\/responses\/Forbidden"},"404":{"$ref":"#\/components\/responses\/NotFound"}}}},"\/api\/user\/{user}\/order":{"get":{"operationId":"manage-account.user-order.index","summary":"List a user's orders, newest first","description":"Unlike other order surfaces, this endpoint eager-loads items\/payments, so those keys are always present (not whenLoaded-gated).","tags":["Subscriptions"],"security":[{"sanctum":[]},{"oauth2":["subscription.view"]}],"parameters":[{"name":"user","in":"path","required":true,"description":"User ULID.","schema":{"type":"string"}},{"name":"page","in":"query","required":false,"description":"Page number (1-indexed).","schema":{"type":"integer"}},{"name":"per_page","in":"query","required":false,"description":"Page size.","schema":{"type":"integer"}}],"responses":{"200":{"description":"Paginated order list.","content":{"application\/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#\/components\/schemas\/Order"}},"links":{"$ref":"#\/components\/schemas\/PaginationLinks"},"meta":{"$ref":"#\/components\/schemas\/PaginationMeta"}},"additionalProperties":false}}}},"401":{"$ref":"#\/components\/responses\/Unauthorized"},"403":{"$ref":"#\/components\/responses\/Forbidden"},"404":{"$ref":"#\/components\/responses\/NotFound"}}}},"\/api\/user\/{user}\/payment":{"get":{"operationId":"manage-account.user-payment.index","summary":"List a user's payments, newest first","tags":["Subscriptions"],"security":[{"sanctum":[]},{"oauth2":["subscription.view"]}],"parameters":[{"name":"user","in":"path","required":true,"description":"User ULID.","schema":{"type":"string"}},{"name":"page","in":"query","required":false,"description":"Page number (1-indexed).","schema":{"type":"integer"}},{"name":"per_page","in":"query","required":false,"description":"Page size.","schema":{"type":"integer"}}],"responses":{"200":{"description":"Paginated payment list.","content":{"application\/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#\/components\/schemas\/Payment"}},"links":{"$ref":"#\/components\/schemas\/PaginationLinks"},"meta":{"$ref":"#\/components\/schemas\/PaginationMeta"}},"additionalProperties":false}}}},"401":{"$ref":"#\/components\/responses\/Unauthorized"},"403":{"$ref":"#\/components\/responses\/Forbidden"},"404":{"$ref":"#\/components\/responses\/NotFound"}}}},"\/api\/user\/{user}\/payment\/{payment}":{"get":{"operationId":"manage-account.user-payment.show","summary":"Get a payment scoped to a user","tags":["Subscriptions"],"security":[{"sanctum":[]},{"oauth2":["subscription.view"]}],"parameters":[{"name":"user","in":"path","required":true,"description":"User ULID.","schema":{"type":"string"}},{"name":"payment","in":"path","required":true,"description":"Payment ID.","schema":{"type":"string"}}],"responses":{"200":{"description":"Payment detail.","content":{"application\/json":{"schema":{"type":"object","properties":{"data":{"$ref":"#\/components\/schemas\/Payment"}},"additionalProperties":false}}}},"401":{"$ref":"#\/components\/responses\/Unauthorized"},"403":{"$ref":"#\/components\/responses\/Forbidden"},"404":{"$ref":"#\/components\/responses\/NotFound"}}}},"\/api\/user\/{user}\/device":{"get":{"operationId":"manage-account.user-device.index","summary":"List a user's devices (owned + accessible via team)","tags":["Subscriptions"],"security":[{"sanctum":[]},{"oauth2":["subscription.view"]}],"parameters":[{"name":"user","in":"path","required":true,"description":"User ULID.","schema":{"type":"string"}},{"name":"categories","in":"query","required":false,"description":"Filter by one or more device category enum values.","schema":{"type":"array","items":{"type":"string"}}},{"name":"disabled","in":"query","required":false,"description":"Filter by disabled state.","schema":{"type":"boolean"}},{"name":"is_setup","in":"query","required":false,"description":"Filter to set-up (true) or pending-setup (false) devices.","schema":{"type":"boolean"}},{"name":"driving","in":"query","required":false,"description":"Filter by ignition-on (true) \/ ignition-off (false).","schema":{"type":"boolean"}},{"name":"online","in":"query","required":false,"description":"Filter by online (true) \/ offline (false) connectivity.","schema":{"type":"boolean"}},{"name":"q","in":"query","required":false,"description":"Search by device\/vehicle name.","schema":{"type":"string"}},{"name":"per_page","in":"query","required":false,"description":"Page size, capped at 100 (default 50).","schema":{"type":"integer"}},{"name":"page","in":"query","required":false,"description":"Page number (1-indexed).","schema":{"type":"integer"}}],"responses":{"200":{"description":"Paginated device list.","content":{"application\/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#\/components\/schemas\/Device"}},"links":{"$ref":"#\/components\/schemas\/PaginationLinks"},"meta":{"$ref":"#\/components\/schemas\/PaginationMeta"}},"additionalProperties":false}}}},"401":{"$ref":"#\/components\/responses\/Unauthorized"},"403":{"$ref":"#\/components\/responses\/Forbidden"},"404":{"$ref":"#\/components\/responses\/NotFound"}}}},"\/api\/team":{"get":{"operationId":"manage-account.team.index","summary":"List teams (admin-scoped; requires team.view_any)","tags":["Teams"],"security":[{"sanctum":[]},{"oauth2":["team.view"]}],"parameters":[{"name":"is_root","in":"query","required":false,"description":"true filters to root (top-level) teams; false to child teams only.","schema":{"type":"boolean"}},{"name":"page","in":"query","required":false,"description":"Page number (1-indexed).","schema":{"type":"integer"}},{"name":"per_page","in":"query","required":false,"description":"Page size.","schema":{"type":"integer"}}],"responses":{"200":{"description":"Paginated team list.","content":{"application\/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#\/components\/schemas\/Team"}},"links":{"$ref":"#\/components\/schemas\/PaginationLinks"},"meta":{"$ref":"#\/components\/schemas\/PaginationMeta"}},"additionalProperties":false}}}},"401":{"$ref":"#\/components\/responses\/Unauthorized"},"403":{"$ref":"#\/components\/responses\/Forbidden"}}},"post":{"operationId":"manage-account.team.store","summary":"Create a team","description":"422 once at the plan's team cap. The creator is attached as a member with every team.* permission.","tags":["Teams"],"security":[{"sanctum":[]},{"oauth2":["team.manage"]}],"parameters":[],"requestBody":{"required":true,"content":{"application\/json":{"schema":{"type":"object","properties":{"name":{"type":"string"},"email":{"type":"string"},"phone":{"type":["string","null"]},"parent_id":{"type":["string","null"],"description":"Parent team ULID; must be one of the caller's own teams."}},"required":["name","email"],"additionalProperties":false}}}},"responses":{"200":{"description":"Created team.","content":{"application\/json":{"schema":{"type":"object","properties":{"data":{"$ref":"#\/components\/schemas\/Team"}},"additionalProperties":false}}}},"401":{"$ref":"#\/components\/responses\/Unauthorized"},"403":{"$ref":"#\/components\/responses\/Forbidden"},"422":{"$ref":"#\/components\/responses\/ValidationError"}}}},"\/api\/team\/{team}":{"get":{"operationId":"manage-account.team.show","summary":"Get a team","tags":["Teams"],"security":[{"sanctum":[]},{"oauth2":["team.view"]}],"parameters":[{"name":"team","in":"path","required":true,"description":"Team ULID. Authorization is membership-scoped \u2014 the caller must belong to this team.","schema":{"type":"string"}}],"responses":{"200":{"description":"Team detail.","content":{"application\/json":{"schema":{"type":"object","properties":{"data":{"$ref":"#\/components\/schemas\/Team"}},"additionalProperties":false}}}},"401":{"$ref":"#\/components\/responses\/Unauthorized"},"403":{"$ref":"#\/components\/responses\/Forbidden"},"404":{"$ref":"#\/components\/responses\/NotFound"}}},"put":{"operationId":"manage-account.team.update","summary":"Update a team","tags":["Teams"],"security":[{"sanctum":[]},{"oauth2":["team.manage"]}],"parameters":[{"name":"team","in":"path","required":true,"description":"Team ULID. Authorization is membership-scoped \u2014 the caller must belong to this team.","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application\/json":{"schema":{"type":"object","properties":{"name":{"type":"string"},"email":{"type":"string"},"phone":{"type":["string","null"]},"parent_id":{"type":["string","null"],"description":"Parent team ULID; must be one of the caller's own teams."}},"required":["name","email"],"additionalProperties":false}}}},"responses":{"200":{"description":"Updated team.","content":{"application\/json":{"schema":{"type":"object","properties":{"data":{"$ref":"#\/components\/schemas\/Team"}},"additionalProperties":false}}}},"401":{"$ref":"#\/components\/responses\/Unauthorized"},"403":{"$ref":"#\/components\/responses\/Forbidden"},"404":{"$ref":"#\/components\/responses\/NotFound"},"422":{"$ref":"#\/components\/responses\/ValidationError"}}},"delete":{"operationId":"manage-account.team.destroy","summary":"Delete a team","tags":["Teams"],"security":[{"sanctum":[]},{"oauth2":["team.manage"]}],"parameters":[{"name":"team","in":"path","required":true,"description":"Team ULID. Authorization is membership-scoped \u2014 the caller must belong to this team.","schema":{"type":"string"}}],"responses":{"204":{"description":"Deleted; releases one teams allowance unit."},"401":{"$ref":"#\/components\/responses\/Unauthorized"},"403":{"$ref":"#\/components\/responses\/Forbidden"},"404":{"$ref":"#\/components\/responses\/NotFound"}}}},"\/api\/team\/{team}\/user":{"get":{"operationId":"manage-account.team-user.index","summary":"List a team's members","tags":["Teams"],"security":[{"sanctum":[]},{"oauth2":["team.view"]}],"parameters":[{"name":"team","in":"path","required":true,"description":"Team ULID. Authorization is membership-scoped \u2014 the caller must belong to this team.","schema":{"type":"string"}},{"name":"limit","in":"query","required":false,"description":"Cap the result count (applied before pagination).","schema":{"type":"integer"}},{"name":"per_page","in":"query","required":false,"description":"Page size, capped at 100 (default 15).","schema":{"type":"integer"}},{"name":"page","in":"query","required":false,"description":"Page number (1-indexed).","schema":{"type":"integer"}}],"responses":{"200":{"description":"Paginated member list.","content":{"application\/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#\/components\/schemas\/TeamMember"}},"links":{"$ref":"#\/components\/schemas\/PaginationLinks"},"meta":{"$ref":"#\/components\/schemas\/PaginationMeta"}},"additionalProperties":false}}}},"401":{"$ref":"#\/components\/responses\/Unauthorized"},"403":{"$ref":"#\/components\/responses\/Forbidden"},"404":{"$ref":"#\/components\/responses\/NotFound"}}},"post":{"operationId":"manage-account.team-user.store","summary":"Invite a member to a team by email","description":"The invite is cached for 7 days pending acceptance (no team-membership row is created yet); an email invitation is dispatched after the response.","tags":["Teams"],"security":[{"sanctum":[]},{"oauth2":["team.manage"]}],"parameters":[{"name":"team","in":"path","required":true,"description":"Team ULID. Authorization is membership-scoped \u2014 the caller must belong to this team.","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application\/json":{"schema":{"type":"object","properties":{"email":{"type":"string"},"name":{"type":["string","null"]},"permissions":{"type":"array","items":{"type":"string","description":"Permission slug (see \/api\/permission), granted to the invitee once they accept."}}},"required":["email"],"additionalProperties":false}}}},"responses":{"204":{"description":"Invitation queued."},"401":{"$ref":"#\/components\/responses\/Unauthorized"},"403":{"$ref":"#\/components\/responses\/Forbidden"},"404":{"$ref":"#\/components\/responses\/NotFound"},"422":{"$ref":"#\/components\/responses\/ValidationError"}}}},"\/api\/team\/{team}\/user\/{user}":{"get":{"operationId":"manage-account.team-user.show","summary":"Get a team member","tags":["Teams"],"security":[{"sanctum":[]},{"oauth2":["team.view"]}],"parameters":[{"name":"team","in":"path","required":true,"description":"Team ULID. Authorization is membership-scoped \u2014 the caller must belong to this team.","schema":{"type":"string"}},{"name":"user","in":"path","required":true,"description":"Member's user ULID.","schema":{"type":"string"}}],"responses":{"200":{"description":"Member detail.","content":{"application\/json":{"schema":{"type":"object","properties":{"data":{"$ref":"#\/components\/schemas\/TeamMember"}},"additionalProperties":false}}}},"401":{"$ref":"#\/components\/responses\/Unauthorized"},"403":{"$ref":"#\/components\/responses\/Forbidden"},"404":{"$ref":"#\/components\/responses\/NotFound"}}},"put":{"operationId":"manage-account.team-user.update","summary":"Update a team member's permissions","tags":["Teams"],"security":[{"sanctum":[]},{"oauth2":["team.manage"]}],"parameters":[{"name":"team","in":"path","required":true,"description":"Team ULID. Authorization is membership-scoped \u2014 the caller must belong to this team.","schema":{"type":"string"}},{"name":"user","in":"path","required":true,"description":"Member's user ULID.","schema":{"type":"string"}}],"requestBody":{"required":false,"content":{"application\/json":{"schema":{"type":"object","properties":{"permissions":{"type":"array","items":{"type":"string","description":"Permission slug (see \/api\/permission)."}}},"additionalProperties":false}}}},"responses":{"200":{"description":"Updated member.","content":{"application\/json":{"schema":{"type":"object","properties":{"data":{"$ref":"#\/components\/schemas\/TeamMember"}},"additionalProperties":false}}}},"401":{"$ref":"#\/components\/responses\/Unauthorized"},"403":{"$ref":"#\/components\/responses\/Forbidden"},"404":{"$ref":"#\/components\/responses\/NotFound"},"422":{"$ref":"#\/components\/responses\/ValidationError"}}},"delete":{"operationId":"manage-account.team-user.destroy","summary":"Remove a member from a team","tags":["Teams"],"security":[{"sanctum":[]},{"oauth2":["team.manage"]}],"parameters":[{"name":"team","in":"path","required":true,"description":"Team ULID. Authorization is membership-scoped \u2014 the caller must belong to this team.","schema":{"type":"string"}},{"name":"user","in":"path","required":true,"description":"Member's user ULID.","schema":{"type":"string"}}],"responses":{"204":{"description":"Removed; releases one shared-accounts allowance unit for the team."},"401":{"$ref":"#\/components\/responses\/Unauthorized"},"403":{"$ref":"#\/components\/responses\/Forbidden"},"404":{"$ref":"#\/components\/responses\/NotFound"}}}},"\/api\/team\/{team}\/device":{"get":{"operationId":"manage-account.team-device.index","summary":"List devices belonging to a team","tags":["Teams"],"security":[{"sanctum":[]},{"oauth2":["team.view"]}],"parameters":[{"name":"team","in":"path","required":true,"description":"Team ULID.","schema":{"type":"string"}},{"name":"page","in":"query","required":false,"description":"Page number (1-indexed).","schema":{"type":"integer"}},{"name":"per_page","in":"query","required":false,"description":"Page size.","schema":{"type":"integer"}}],"responses":{"200":{"description":"Paginated device list.","content":{"application\/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#\/components\/schemas\/Device"}},"links":{"$ref":"#\/components\/schemas\/PaginationLinks"},"meta":{"$ref":"#\/components\/schemas\/PaginationMeta"}},"additionalProperties":false}}}},"401":{"$ref":"#\/components\/responses\/Unauthorized"},"403":{"$ref":"#\/components\/responses\/Forbidden"},"404":{"$ref":"#\/components\/responses\/NotFound"}}}},"\/api\/team\/{team}\/device\/{device}":{"delete":{"operationId":"manage-account.team-device.destroy","summary":"Unassign a device from a team","description":"Clears the device's team assignment. The device, its history, and its subscription are left intact. 404 when the device is not assigned to this team. Returns 422 when nobody owns the device outside the team and it still has an active subscription, because that would leave a paid device nobody can manage.","tags":["Teams"],"security":[{"sanctum":[]},{"oauth2":["team.manage"]}],"parameters":[{"name":"team","in":"path","required":true,"description":"Team ULID.","schema":{"type":"string"}},{"name":"device","in":"path","required":true,"description":"Device ULID. Must currently be assigned to this team.","schema":{"type":"string"}}],"responses":{"204":{"description":"Unassigned. The device itself still exists."},"401":{"$ref":"#\/components\/responses\/Unauthorized"},"403":{"$ref":"#\/components\/responses\/Forbidden"},"404":{"$ref":"#\/components\/responses\/NotFound"}}}},"\/api\/user\/{user}\/expo-push-token":{"get":{"operationId":"manage-account.expo-push-token.index","summary":"List a user's registered Expo push tokens","tags":["Notifications"],"security":[{"sanctum":[]},{"oauth2":["notification.view"]}],"parameters":[{"name":"user","in":"path","required":true,"description":"User ULID.","schema":{"type":"string"}},{"name":"page","in":"query","required":false,"description":"Page number (1-indexed).","schema":{"type":"integer"}},{"name":"per_page","in":"query","required":false,"description":"Page size.","schema":{"type":"integer"}}],"responses":{"200":{"description":"Paginated token list.","content":{"application\/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"type":"object","properties":{"token":{"type":"string","description":"Sensitive identifier (Expo push token)."},"data":{"type":"object","properties":{"device_name":{"type":["string","null"]},"device_os":{"type":["string","null"]},"device_brand":{"type":["string","null"]}},"additionalProperties":true}},"required":["token"],"additionalProperties":false}},"links":{"$ref":"#\/components\/schemas\/PaginationLinks"},"meta":{"$ref":"#\/components\/schemas\/PaginationMeta"}},"additionalProperties":false}}}},"401":{"$ref":"#\/components\/responses\/Unauthorized"},"403":{"$ref":"#\/components\/responses\/Forbidden"},"404":{"$ref":"#\/components\/responses\/NotFound"}}},"post":{"operationId":"manage-account.expo-push-token.store","summary":"Register (or re-register) an Expo push token for a device","description":"Idempotent on token \u2014 calling again with the same token updates data instead of duplicating the row.","tags":["Notifications"],"security":[{"sanctum":[]},{"oauth2":["notification.manage"]}],"parameters":[{"name":"user","in":"path","required":true,"description":"User ULID.","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application\/json":{"schema":{"type":"object","properties":{"token":{"type":"string","description":"Sensitive identifier (Expo push token)."},"device_name":{"type":["string","null"]},"device_os":{"type":["string","null"]},"device_brand":{"type":["string","null"]}},"required":["token"],"additionalProperties":false}}}},"responses":{"200":{"description":"Registered token.","content":{"application\/json":{"schema":{"type":"object","properties":{"data":{"type":"object","properties":{"token":{"type":"string","description":"Sensitive identifier (Expo push token)."},"data":{"type":"object","properties":{"device_name":{"type":["string","null"]},"device_os":{"type":["string","null"]},"device_brand":{"type":["string","null"]}},"additionalProperties":true}},"required":["token"],"additionalProperties":false}},"additionalProperties":false}}}},"401":{"$ref":"#\/components\/responses\/Unauthorized"},"403":{"$ref":"#\/components\/responses\/Forbidden"},"404":{"$ref":"#\/components\/responses\/NotFound"},"422":{"$ref":"#\/components\/responses\/ValidationError"}}}},"\/api\/user\/{user}\/expo-push-token\/{token}":{"delete":{"operationId":"manage-account.expo-push-token.destroy","summary":"Unregister an Expo push token","tags":["Notifications"],"security":[{"sanctum":[]},{"oauth2":["notification.manage"]}],"parameters":[{"name":"user","in":"path","required":true,"description":"User ULID.","schema":{"type":"string"}},{"name":"token","in":"path","required":true,"description":"The Expo push token value itself (sensitive identifier), not a numeric ID.","schema":{"type":"string"}}],"responses":{"204":{"description":"Unregistered."},"401":{"$ref":"#\/components\/responses\/Unauthorized"},"403":{"$ref":"#\/components\/responses\/Forbidden"},"404":{"$ref":"#\/components\/responses\/NotFound"}}}},"\/api\/user\/{user}\/expo-push-token\/{token}\/notification\/{notificationId}\/read":{"post":{"operationId":"manage-account.expo-push-token-notification.read","summary":"Mark a push notification as read","tags":["Notifications"],"security":[{"sanctum":[]},{"oauth2":["notification.manage"]}],"parameters":[{"name":"user","in":"path","required":true,"description":"User ULID.","schema":{"type":"string"}},{"name":"token","in":"path","required":true,"description":"The Expo push token value itself (sensitive identifier), not a numeric ID.","schema":{"type":"string"}},{"name":"notificationId","in":"path","required":true,"description":"Notification ID.","schema":{"type":"string"}}],"responses":{"200":{"description":"Updated notification.","content":{"application\/json":{"schema":{"type":"object","properties":{"data":{"type":"object","description":"Notification record with read_at now set.","additionalProperties":true}},"additionalProperties":false}}}},"401":{"$ref":"#\/components\/responses\/Unauthorized"},"403":{"$ref":"#\/components\/responses\/Forbidden"},"404":{"$ref":"#\/components\/responses\/NotFound"}}}},"\/api\/chatbot":{"post":{"operationId":"support.chatbot.store","summary":"Send a message to the pre-auth assistant \u2014 always guest-scoped, even with a Bearer token","description":"Rate limited. JSON response, not SSE. This route carries no auth:sanctum middleware and never resolves or forwards a caller identity, so every call \u2014 including one sent with a valid Bearer token \u2014 is treated as an unauthenticated session by the tool-dispatch gate: read\/discovery tools plus CREATE_LEAD, REQUEST_HUMAN_HANDOFF, and the verification-code pair (send_verification_code\/verify_code) only. Write tools (place an order, activate\/restart a device, change device settings) are always refused here with a polite \"requires the app \/ signed-in session\" tool result \u2014 signed-in callers who need those must use POST \/api\/chat\/message instead. An empty \"reply\" means a human agent has taken over and the bot is intentionally silent.","tags":["Support"],"security":[],"parameters":[],"requestBody":{"required":true,"content":{"application\/json":{"schema":{"type":"object","properties":{"message":{"type":"string","description":"Max 1000 characters."},"session_id":{"type":["string","null"],"format":"uuid","description":"Continues an existing conversation when provided; a new UUID is generated and echoed back otherwise."},"visitor_fingerprint":{"type":["string","null"],"description":"Anonymous visitor identifier."}},"required":["message"],"additionalProperties":false},"examples":{"default":{"$ref":"#\/components\/examples\/ChatbotRequest"}}}}},"responses":{"200":{"description":"Assistant reply.","content":{"application\/json":{"schema":{"type":"object","properties":{"reply":{"type":"string","description":"Empty string means the bot is intentionally silent (human handoff)."},"session_id":{"type":"string","format":"uuid"}},"required":["reply","session_id"],"additionalProperties":false},"examples":{"default":{"$ref":"#\/components\/examples\/ChatbotResponse"}}}}},"422":{"$ref":"#\/components\/responses\/ValidationError"},"429":{"$ref":"#\/components\/responses\/TooManyRequests"}}}},"\/api\/chat\/message":{"post":{"operationId":"support.chat-message.store","summary":"Send a message to the in-app assistant, as Server-Sent Events or a single JSON body","description":"Content-negotiated on Accept. Send Accept: application\/json for a single JSON body ({reply, conversation_id}) \u2014 the shape every machine caller wants, since the stream carries exactly one useful payload and no token-by-token output. Anything else (including Accept: text\/event-stream, which the app sends) gets Content-Type: text\/event-stream with these events, in order: one \"thinking\", then either one \"reply\" ({text, conversation_id}) or one \"error\" ({message}) on failure, then one terminal \"done\". The JSON variant maps that failure to 500 instead of a 200 carrying an error event. 403 when the caller's account lacks the ai-chat feature flag; 404 when conversation_id is not the caller's. Write-capable tools (place an order, activate\/restart a device, change device settings) require confirming a 6-digit code emailed out-of-band before executing. Rate limited.","tags":["Support"],"security":[{"sanctum":[]},{"oauth2":["support.write"]}],"parameters":[],"requestBody":{"required":true,"content":{"application\/json":{"schema":{"type":"object","properties":{"message":{"type":"string","description":"Max 2000 characters."},"conversation_id":{"type":["string","null"],"description":"Continues an existing conversation; omit to start a new one."},"context":{"type":["object","null"],"description":"Appended to the message as a bracketed app-context suffix, not sent as structured data to the model.","properties":{"source":{"type":["string","null"]},"deviceId":{"type":["string","null"]},"data":{"type":"object","additionalProperties":true}},"additionalProperties":true}},"required":["message"],"additionalProperties":false}}}},"responses":{"200":{"description":"Assistant reply \u2014 SSE frames by default, a single JSON body when the caller sends Accept: application\/json.","content":{"text\/event-stream":{"schema":{"type":"string","description":"Raw SSE frames; no single JSON schema applies (see operation description for the per-event-type shapes)."}},"application\/json":{"schema":{"type":"object","properties":{"reply":{"type":"string","description":"Empty string means the bot is intentionally silent (human handoff). The SSE variant omits the reply event entirely in that case."},"conversation_id":{"type":["string","null"],"description":"The conversation this reply belongs to; null only when no app conversation could be resolved."}},"required":["reply","conversation_id"],"additionalProperties":false}}}},"404":{"$ref":"#\/components\/responses\/NotFound"},"500":{"$ref":"#\/components\/responses\/ServerError"},"401":{"$ref":"#\/components\/responses\/Unauthorized"},"403":{"$ref":"#\/components\/responses\/Forbidden"},"422":{"$ref":"#\/components\/responses\/ValidationError"},"429":{"$ref":"#\/components\/responses\/TooManyRequests"}}}},"\/api\/chat\/conversations":{"get":{"operationId":"support.chat-conversations.index","summary":"List the caller's in-app chat conversations","description":"403 when the caller's account lacks the ai-chat feature flag.","tags":["Support"],"security":[{"sanctum":[]},{"oauth2":["support.read"]}],"parameters":[],"responses":{"200":{"description":"Conversation list (not paginated).","content":{"application\/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"channel":{"type":"string"},"message_count":{"type":"integer"},"last_message_preview":{"type":["string","null"],"description":"First 80 characters of the last user message."},"last_message_at":{"type":["string","null"],"format":"date-time"},"handed_off":{"type":"boolean"},"taken_over":{"type":"boolean","description":"True once a human agent has actually taken over (distinct from handed_off, which means \"awaiting agent\")."},"created_at":{"type":["string","null"],"format":"date-time"},"updated_at":{"type":["string","null"],"format":"date-time"}},"required":["id","channel"],"additionalProperties":false}}},"additionalProperties":false}}}},"401":{"$ref":"#\/components\/responses\/Unauthorized"},"403":{"$ref":"#\/components\/responses\/Forbidden"}}},"post":{"operationId":"support.chat-conversations.store","summary":"Start a new conversation","description":"403 when the caller's account lacks the ai-chat feature flag. 422 once the caller is at their conversation cap (default 10) \u2014 the response then carries error, oldest_id, and oldest_preview instead of a conversation, so the client can prompt to delete the oldest one.","tags":["Support"],"security":[{"sanctum":[]},{"oauth2":["support.write"]}],"parameters":[],"responses":{"201":{"description":"Created conversation.","content":{"application\/json":{"schema":{"type":"object","properties":{"data":{"$ref":"#\/components\/schemas\/ChatConversation"}},"additionalProperties":false}}}},"422":{"description":"Conversation cap reached (default 10). Not a validation error \u2014 delete a conversation and retry.","content":{"application\/json":{"schema":{"type":"object","properties":{"error":{"type":"string","enum":["conversation_limit_reached"]},"oldest_id":{"type":["string","null"],"description":"The caller's oldest conversation, offered as the one to delete."},"oldest_preview":{"type":["string","null"],"description":"First 60 characters of that conversation's last user message."}},"required":["error","oldest_id","oldest_preview"],"additionalProperties":false}}}},"401":{"$ref":"#\/components\/responses\/Unauthorized"},"403":{"$ref":"#\/components\/responses\/Forbidden"}}}},"\/api\/chat\/conversations\/{id}":{"get":{"operationId":"support.chat-conversations.show","summary":"Get a conversation with its full message history","tags":["Support"],"security":[{"sanctum":[]},{"oauth2":["support.read"]}],"parameters":[{"name":"id","in":"path","required":true,"description":"Conversation ID.","schema":{"type":"string"}}],"responses":{"200":{"description":"Conversation detail.","content":{"application\/json":{"schema":{"type":"object","properties":{"data":{"$ref":"#\/components\/schemas\/ChatConversation"}},"additionalProperties":false}}}},"401":{"$ref":"#\/components\/responses\/Unauthorized"},"403":{"$ref":"#\/components\/responses\/Forbidden"},"404":{"$ref":"#\/components\/responses\/NotFound"}}},"delete":{"operationId":"support.chat-conversations.destroy","summary":"Delete a conversation","tags":["Support"],"security":[{"sanctum":[]},{"oauth2":["support.write"]}],"parameters":[{"name":"id","in":"path","required":true,"description":"Conversation ID.","schema":{"type":"string"}}],"responses":{"204":{"description":"Deleted."},"401":{"$ref":"#\/components\/responses\/Unauthorized"},"403":{"$ref":"#\/components\/responses\/Forbidden"},"404":{"$ref":"#\/components\/responses\/NotFound"}}}},"\/api\/chat\/conversations\/{id}\/messages":{"delete":{"operationId":"support.chat-conversations-messages.destroy","summary":"Clear a conversation's messages, keeping the conversation itself","tags":["Support"],"security":[{"sanctum":[]},{"oauth2":["support.write"]}],"parameters":[{"name":"id","in":"path","required":true,"description":"Conversation ID.","schema":{"type":"string"}}],"responses":{"204":{"description":"Messages cleared."},"401":{"$ref":"#\/components\/responses\/Unauthorized"},"403":{"$ref":"#\/components\/responses\/Forbidden"},"404":{"$ref":"#\/components\/responses\/NotFound"}}}},"\/api\/user\/{user}\/appointment":{"get":{"operationId":"support.user-appointment.index","summary":"List a user's appointments (as client) or assigned appointments (as technician)","tags":["Appointments"],"security":[{"sanctum":[]},{"oauth2":["appointment.view"]}],"parameters":[{"name":"user","in":"path","required":true,"description":"User ULID.","schema":{"type":"string"}},{"name":"status","in":"query","required":false,"description":"Derived appointment status.","schema":{"type":"string","enum":["pending","confirmed","awaiting_client","awaiting_technician","completed","cancelled"]}},{"name":"upcoming","in":"query","required":false,"description":"true filters to scheduled_at >= today; false to the past.","schema":{"type":"boolean"}},{"name":"device_ulid","in":"query","required":false,"description":"Filter to appointments for one device.","schema":{"type":"string"}},{"name":"date","in":"query","required":false,"description":"Filter to one scheduled date (Y-m-d).","schema":{"type":"string","format":"date"}},{"name":"search","in":"query","required":false,"description":"Match client name\/phone or device name.","schema":{"type":"string"}},{"name":"order","in":"query","required":false,"description":"Ordered by scheduled_at (default desc).","schema":{"type":"string","enum":["asc","desc"]}},{"name":"per_page","in":"query","required":false,"description":"Page size (default 15).","schema":{"type":"integer"}},{"name":"page","in":"query","required":false,"description":"Page number (1-indexed).","schema":{"type":"integer"}}],"responses":{"200":{"description":"Paginated appointment list.","content":{"application\/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#\/components\/schemas\/Appointment"}},"links":{"$ref":"#\/components\/schemas\/PaginationLinks"},"meta":{"$ref":"#\/components\/schemas\/PaginationMeta"}},"additionalProperties":false}}}},"401":{"$ref":"#\/components\/responses\/Unauthorized"},"403":{"$ref":"#\/components\/responses\/Forbidden"},"404":{"$ref":"#\/components\/responses\/NotFound"}}},"post":{"operationId":"support.user-appointment.store","summary":"Schedule a technician appointment","description":"device_ulid must resolve to a device the caller can view, or 404. technician_ulid, when set, must resolve to a technician user, or 404.","tags":["Appointments"],"security":[{"sanctum":[]},{"oauth2":["appointment.create"]}],"parameters":[{"name":"user","in":"path","required":true,"description":"User ULID.","schema":{"type":"string"}},{"name":"Idempotency-Key","in":"header","required":false,"description":"Optional client-supplied key (max 255 chars). The first successful (2xx) response for a key is cached 24h and replayed verbatim on retry with the same request body (adds Idempotency-Replayed: true). The same key with a different body 422s; a concurrent request reusing an in-flight key 409s instead of executing twice.","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/AppointmentRequest"}}}},"responses":{"200":{"description":"Created appointment.","content":{"application\/json":{"schema":{"type":"object","properties":{"data":{"$ref":"#\/components\/schemas\/Appointment"}},"additionalProperties":false}}}},"401":{"$ref":"#\/components\/responses\/Unauthorized"},"403":{"$ref":"#\/components\/responses\/Forbidden"},"404":{"$ref":"#\/components\/responses\/NotFound"},"422":{"$ref":"#\/components\/responses\/ValidationError"}}}},"\/api\/user\/{user}\/appointment\/{appointment}":{"get":{"operationId":"support.user-appointment.show","summary":"Get an appointment","tags":["Appointments"],"security":[{"sanctum":[]},{"oauth2":["appointment.view"]}],"parameters":[{"name":"user","in":"path","required":true,"description":"User ULID.","schema":{"type":"string"}},{"name":"appointment","in":"path","required":true,"description":"Appointment ULID.","schema":{"type":"string"}}],"responses":{"200":{"description":"Appointment detail.","content":{"application\/json":{"schema":{"type":"object","properties":{"data":{"$ref":"#\/components\/schemas\/Appointment"}},"additionalProperties":false}}}},"401":{"$ref":"#\/components\/responses\/Unauthorized"},"403":{"$ref":"#\/components\/responses\/Forbidden"},"404":{"$ref":"#\/components\/responses\/NotFound"}}},"put":{"operationId":"support.user-appointment.update","summary":"Reschedule or update an appointment","tags":["Appointments"],"security":[{"sanctum":[]},{"oauth2":["appointment.update"]}],"parameters":[{"name":"user","in":"path","required":true,"description":"User ULID.","schema":{"type":"string"}},{"name":"appointment","in":"path","required":true,"description":"Appointment ULID.","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/AppointmentRequest"}}}},"responses":{"200":{"description":"Updated appointment.","content":{"application\/json":{"schema":{"type":"object","properties":{"data":{"$ref":"#\/components\/schemas\/Appointment"}},"additionalProperties":false}}}},"401":{"$ref":"#\/components\/responses\/Unauthorized"},"403":{"$ref":"#\/components\/responses\/Forbidden"},"404":{"$ref":"#\/components\/responses\/NotFound"},"422":{"$ref":"#\/components\/responses\/ValidationError"}}},"delete":{"operationId":"support.user-appointment.destroy","summary":"Cancel an appointment","description":"Soft-cancels (sets cancelled_at) rather than deleting the record.","tags":["Appointments"],"security":[{"sanctum":[]},{"oauth2":["appointment.delete"]}],"parameters":[{"name":"user","in":"path","required":true,"description":"User ULID.","schema":{"type":"string"}},{"name":"appointment","in":"path","required":true,"description":"Appointment ULID.","schema":{"type":"string"}}],"requestBody":{"required":false,"content":{"application\/json":{"schema":{"type":"object","properties":{"reason":{"type":["string","null"]}},"additionalProperties":false}}}},"responses":{"204":{"description":"Cancelled."},"401":{"$ref":"#\/components\/responses\/Unauthorized"},"403":{"$ref":"#\/components\/responses\/Forbidden"},"404":{"$ref":"#\/components\/responses\/NotFound"},"422":{"$ref":"#\/components\/responses\/ValidationError"}}}},"\/api\/appointment\/{appointment}\/complete":{"post":{"operationId":"support.appointment-complete.store","summary":"Mark an appointment as completed (technician-side)","description":"422 when the appointment is already completed or was cancelled.","tags":["Appointments"],"security":[{"sanctum":[]},{"oauth2":["appointment.update"]}],"parameters":[{"name":"appointment","in":"path","required":true,"description":"Appointment ULID.","schema":{"type":"string"}}],"requestBody":{"required":false,"content":{"application\/json":{"schema":{"type":"object","properties":{"checklist":{"type":"array","items":{"type":"object","properties":{"item":{"type":"string"},"passed":{"type":"boolean"}},"required":["item","passed"],"additionalProperties":false}},"notes":{"type":["string","null"],"description":"Max 5000 characters."}},"additionalProperties":false}}}},"responses":{"200":{"description":"Completed appointment.","content":{"application\/json":{"schema":{"type":"object","properties":{"data":{"$ref":"#\/components\/schemas\/Appointment"}},"additionalProperties":false}}}},"401":{"$ref":"#\/components\/responses\/Unauthorized"},"403":{"$ref":"#\/components\/responses\/Forbidden"},"404":{"$ref":"#\/components\/responses\/NotFound"},"422":{"$ref":"#\/components\/responses\/ValidationError"}}}},"\/api\/user\/{user}\/qualify\/use-case":{"get":{"operationId":"support.user-qualify-use-case.show","summary":"Get use-case options for lead qualification, plus the caller's current selection","description":"Response also includes a deprecated \"industries\" key mirroring \"use_cases\", kept for mobile app < 0.76.22 (removal planned 2026-09-29).","tags":["Support"],"security":[{"sanctum":[]},{"oauth2":["support.read"]}],"parameters":[{"name":"user","in":"path","required":true,"description":"User ULID.","schema":{"type":"string"}}],"responses":{"200":{"description":"Use-case options.","content":{"application\/json":{"schema":{"type":"object","properties":{"current":{"type":["string","null"]},"use_cases":{"type":"array","items":{"type":"object","properties":{"value":{"type":"string"},"label":{"type":"string"},"description":{"type":"string"}},"additionalProperties":false}},"industries":{"type":"array","items":{"type":"object","properties":{"value":{"type":"string"},"label":{"type":"string"},"description":{"type":"string"}},"additionalProperties":false}}},"required":["use_cases"],"additionalProperties":false}}}},"401":{"$ref":"#\/components\/responses\/Unauthorized"},"403":{"$ref":"#\/components\/responses\/Forbidden"},"404":{"$ref":"#\/components\/responses\/NotFound"}}},"post":{"operationId":"support.user-qualify-use-case.store","summary":"Record the caller's use case, advancing the lead-qualification funnel","tags":["Support"],"security":[{"sanctum":[]},{"oauth2":["support.write"]}],"parameters":[{"name":"user","in":"path","required":true,"description":"User ULID.","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application\/json":{"schema":{"type":"object","properties":{"use_case":{"type":"string","description":"UseCaseEnum value, or \"other\"."},"use_case_other":{"type":["string","null"],"description":"Required when use_case is \"other\", max 255 characters."}},"required":["use_case"],"additionalProperties":false}}}},"responses":{"200":{"description":"Next funnel step.","content":{"application\/json":{"schema":{"type":"object","properties":{"next":{"type":"string","description":"Always \"operating-area\" \u2014 the next legacy funnel step (not itself documented in this API surface)."}},"required":["next"],"additionalProperties":false}}}},"401":{"$ref":"#\/components\/responses\/Unauthorized"},"403":{"$ref":"#\/components\/responses\/Forbidden"},"404":{"$ref":"#\/components\/responses\/NotFound"},"422":{"$ref":"#\/components\/responses\/ValidationError"}}}}},"x-assistant-features":{"availability_values":["available","read_only","admin_only","requires_setup","requires_feature","requires_permission"],"capabilities":[{"slug":"predictive-maintenance","label":"Predictive Maintenance","description":"Schedule and track vehicle maintenance based on mileage, engine hours, and predictive analytics. Never miss an oil change or inspection again.","tagline":"Automated maintenance scheduling that prevents breakdowns","category":"manage","category_label":"Manage & Optimize","minimum_plan":"premium","availability":["available","requires_feature","requires_permission","requires_setup"],"setup_steps":[{"title":"Set service rules","body":"Add oil-change, inspection, tire, or custom service intervals based on mileage, dates, or fleet policy."},{"title":"Attach vehicle history","body":"Keep service notes and completed work tied to the vehicle record instead of a separate spreadsheet."},{"title":"Review upcoming work","body":"Use the maintenance queue to decide what should be booked before a vehicle misses its next service window."}],"proof_points":["Maintenance work is tied back to vehicle usage instead of memory or paper notes.","Managers can see upcoming service before assigning a vehicle to the next job."],"benefits":["Schedule based on mileage or time","Get reminded before issues occur","Track service history","Reduce unexpected breakdowns"],"use_cases":["Oil change scheduling","Tire rotation reminders","Inspection planning"],"internal_links":[{"label":"Getting Started with Fleet Tracking","description":"Set up vehicle records before relying on maintenance reminders.","url":"https:\/\/traxelio.com\/guides\/getting-started-fleet-tracking"},{"label":"Construction & Heavy Equipment","description":"See how maintenance fits fleets with equipment and site vehicles.","url":"https:\/\/traxelio.com\/use-cases\/construction"},{"label":"Compare maintenance-ready plans","description":"Review current plan options before rolling reminders across the fleet.","url":"https:\/\/traxelio.com\/pricing"}],"url":"https:\/\/traxelio.com\/features\/predictive-maintenance","operation_ids":["operate.device-inspection.index","operate.device-inspection.store","operate.device-issue.index","operate.device-issue.store","operate.device-troubleshooting-power-off.show"]},{"slug":"documents","label":"Document Storage","description":"Store and manage important fleet documents.","tagline":"Never miss an insurance renewal again","category":"manage","category_label":"Manage & Optimize","minimum_plan":"premium","availability":["available","requires_feature","requires_permission"],"setup_steps":[{"title":"Enable the feature","body":"Turn on Document Storage for the vehicles and team members that need this workflow."},{"title":"Review the first signals","body":"Check the first trips, alerts, or reports to confirm the data matches how your fleet operates."},{"title":"Turn data into action","body":"Use the saved history to decide what to dispatch, investigate, maintain, or discuss with the team."}],"proof_points":["The feature is backed by GPS timestamps, vehicle history, and account-level access controls.","Managers can review the record later instead of relying on calls, memory, or screenshots."],"benefits":["Store insurance, registration, and more","Automatic expiry reminders","Upload photos and PDFs","Never miss a renewal deadline"],"use_cases":["Insurance renewal tracking","Vehicle registration management","Inspection scheduling"],"internal_links":[{"label":"Compare GPS tracking plans","description":"See where Document Storage fits in the current Traxelio plan structure.","url":"https:\/\/traxelio.com\/pricing"},{"label":"Getting Started with Fleet Tracking","description":"Use the setup guide to turn tracker data into a working fleet dashboard.","url":"https:\/\/traxelio.com\/guides\/getting-started-fleet-tracking"},{"label":"Talk to Traxelio","description":"Ask which tracker and workflow fit your vehicles.","url":"https:\/\/traxelio.com\/lead\/new"}],"url":"https:\/\/traxelio.com\/features\/documents","operation_ids":["operate.device-document.index","operate.device-document.store","operate.device-document.show","operate.device-document.update","operate.device-document.destroy"]},{"slug":"activity-reports","label":"Activity Reports","description":"Automated reports on vehicle activity with fleet health scoring. Each vehicle is rated active, warning, or critical based on idle time, with distance, drive hours, and downtime tracked.","tagline":"Your fleet's performance at a glance","category":"track","category_label":"Track & Monitor","minimum_plan":"premium","availability":["available","requires_feature","requires_permission"],"setup_steps":[{"title":"Enable the feature","body":"Turn on Activity Reports for the vehicles and team members that need this workflow."},{"title":"Review the first signals","body":"Check the first trips, alerts, or reports to confirm the data matches how your fleet operates."},{"title":"Turn data into action","body":"Use the saved history to decide what to dispatch, investigate, maintain, or discuss with the team."}],"proof_points":["The feature is backed by GPS timestamps, vehicle history, and account-level access controls.","Managers can review the record later instead of relying on calls, memory, or screenshots."],"benefits":["Weekly fleet health digest by email","Vehicle risk tiers: active, warning, critical","Per-vehicle distance, drive time, and idle tracking","Monthly downtime analysis","Team and sub-team level reporting","Export to PDF or CSV"],"use_cases":["Fleet managers reviewing weekly vehicle utilization","Operations teams identifying idle vehicles for reallocation","Client reporting for logistics and car rental companies","Compliance documentation with timestamped activity data"],"internal_links":[{"label":"Compare GPS tracking plans","description":"See where Activity Reports fits in the current Traxelio plan structure.","url":"https:\/\/traxelio.com\/pricing"},{"label":"Getting Started with Fleet Tracking","description":"Use the setup guide to turn tracker data into a working fleet dashboard.","url":"https:\/\/traxelio.com\/guides\/getting-started-fleet-tracking"},{"label":"Talk to Traxelio","description":"Ask which tracker and workflow fit your vehicles.","url":"https:\/\/traxelio.com\/lead\/new"}],"url":"https:\/\/traxelio.com\/features\/activity-reports","operation_ids":["operate.device-reports-time-series.show","operate.device-odometer.show","operate.device-distance.show","operate.device-trip.index"]},{"slug":"alert-notifications","label":"Alert Notifications","description":"Receive instant notifications for critical vehicle events.","tagline":"Never miss a critical event","category":"protect","category_label":"Protect & Control","minimum_plan":"basic","availability":["available","requires_permission"],"setup_steps":[{"title":"Pick alert types","body":"Select movement, geofence, speed, ignition, device, fuel, or maintenance alerts for each operating workflow."},{"title":"Route each alert","body":"Send urgent events to operators and review-only events to managers or weekly reports."},{"title":"Tune noisy rules","body":"Adjust thresholds after the first week so alerts remain useful instead of becoming background noise."}],"proof_points":["Alert routing separates urgent operations events from reports that can wait for review.","Event history gives managers a record of what happened after the notification was sent."],"benefits":["Instant push notifications","SMS and email alerts","Customizable alert types","Never miss critical events"],"use_cases":["Fleet managers staying informed","Rental companies monitoring violations","Security teams responding to theft"],"internal_links":[{"label":"Small Business Fleet","description":"See alert routing for owners who cannot watch the dashboard all day.","url":"https:\/\/traxelio.com\/use-cases\/small-business-fleet"},{"label":"Geo-Zones","description":"Pair alerts with zone entry and exit rules.","url":"https:\/\/traxelio.com\/features\/geo-zone"},{"label":"Compare alert-ready plans","description":"Check which plan matches the alert types your fleet needs.","url":"https:\/\/traxelio.com\/pricing"}],"url":"https:\/\/traxelio.com\/features\/alert-notifications","operation_ids":["operate.device-event.index","operate.device-event.show","manage-account.expo-push-token.index","manage-account.expo-push-token.store"]},{"slug":"api-access","label":"API Access","description":"Integrate with your existing systems via REST API.","tagline":"Connect your own tools to GPS data","category":"team","category_label":"Team & Access","minimum_plan":"basic","availability":["available","requires_permission"],"setup_steps":[{"title":"Scope the data","body":"Choose which vehicles, trips, alerts, and users should be available to the integration."},{"title":"Create credentials","body":"Generate API access for the approved system and keep ownership with the fleet account."},{"title":"Test event flow","body":"Send sample location and alert events to confirm the receiving system stores the fields your team needs."}],"proof_points":["Vehicle positions, trips, alerts, and driver data can be exported into the reporting stack your team already trusts.","Webhook delivery keeps event ownership explicit: the fleet chooses what gets sent and where it lands."],"benefits":["Full REST API access","Live webhooks","Integrate with your tools","Build custom dashboards"],"use_cases":["Developers building custom solutions","Enterprises integrating with ERP","Data analysts creating reports"],"internal_links":[{"label":"Compare plans for API access","description":"Check which fleet plan fits your integration and support needs.","url":"https:\/\/traxelio.com\/pricing"},{"label":"Ask about an integration","description":"Share your target system and the data you need to move.","url":"https:\/\/traxelio.com\/contact"},{"label":"How to Choose GPS Tracking Software","description":"Review the GPS software criteria before building around an API.","url":"https:\/\/traxelio.com\/guides\/how-to-choose-gps-tracking"}],"url":"https:\/\/traxelio.com\/features\/api-access","operation_ids":["manage-account.auth-sessions.index","manage-account.auth-sessions.destroy"]},{"slug":"geo-zone","label":"Geo-Zones","description":"Set up geographic zones for alerts and compliance.","tagline":"Get alerted when vehicles cross boundaries","category":"protect","category_label":"Protect & Control","minimum_plan":"basic","availability":["available","requires_permission"],"setup_steps":[{"title":"Enable the feature","body":"Turn on Geo-Zones for the vehicles and team members that need this workflow."},{"title":"Review the first signals","body":"Check the first trips, alerts, or reports to confirm the data matches how your fleet operates."},{"title":"Turn data into action","body":"Use the saved history to decide what to dispatch, investigate, maintain, or discuss with the team."}],"proof_points":["The feature is backed by GPS timestamps, vehicle history, and account-level access controls.","Managers can review the record later instead of relying on calls, memory, or screenshots."],"benefits":["Draw custom boundaries on the map","Instant alerts when vehicles enter or exit","Perfect for delivery zones and restricted areas","Unlimited zones per vehicle"],"use_cases":["Rental cars crossing borders","Delivery zones compliance","Construction site boundaries"],"internal_links":[{"label":"Compare GPS tracking plans","description":"See where Geo-Zones fits in the current Traxelio plan structure.","url":"https:\/\/traxelio.com\/pricing"},{"label":"Getting Started with Fleet Tracking","description":"Use the setup guide to turn tracker data into a working fleet dashboard.","url":"https:\/\/traxelio.com\/guides\/getting-started-fleet-tracking"},{"label":"Talk to Traxelio","description":"Ask which tracker and workflow fit your vehicles.","url":"https:\/\/traxelio.com\/lead\/new"}],"url":"https:\/\/traxelio.com\/features\/geo-zone","operation_ids":["operate.geofence.index","operate.geofence.store","operate.device-geo-zone.index","operate.device-geo-zone.store","discover.geozone-template.index","convert.geozone-template-purchase.store"]},{"slug":"vehicle-downtime","label":"Vehicle Downtime","description":"Track periods when vehicles sit idle for extended durations. Identify underused assets, optimize fleet utilization, and get alerts when a vehicle has been inactive beyond your threshold.","tagline":"Spot idle vehicles draining your bottom line","category":"manage","category_label":"Manage & Optimize","minimum_plan":"premium","availability":["available","requires_feature","requires_permission"],"setup_steps":[{"title":"Enable the feature","body":"Turn on Vehicle Downtime for the vehicles and team members that need this workflow."},{"title":"Review the first signals","body":"Check the first trips, alerts, or reports to confirm the data matches how your fleet operates."},{"title":"Turn data into action","body":"Use the saved history to decide what to dispatch, investigate, maintain, or discuss with the team."}],"proof_points":["The feature is backed by GPS timestamps, vehicle history, and account-level access controls.","Managers can review the record later instead of relying on calls, memory, or screenshots."],"benefits":["Identify vehicles sitting idle for days or weeks","Get alerts when a vehicle exceeds your inactivity threshold","Track vehicle activity scores to measure fleet utilization","Make data-driven decisions on fleet sizing and asset rotation"],"use_cases":["Fleet managers identifying underused vehicles for reallocation","Car rental companies maximizing vehicle utilization rates","Operations teams planning maintenance during natural downtime windows"],"internal_links":[{"label":"Compare GPS tracking plans","description":"See where Vehicle Downtime fits in the current Traxelio plan structure.","url":"https:\/\/traxelio.com\/pricing"},{"label":"Getting Started with Fleet Tracking","description":"Use the setup guide to turn tracker data into a working fleet dashboard.","url":"https:\/\/traxelio.com\/guides\/getting-started-fleet-tracking"},{"label":"Talk to Traxelio","description":"Ask which tracker and workflow fit your vehicles.","url":"https:\/\/traxelio.com\/lead\/new"}],"url":"https:\/\/traxelio.com\/features\/vehicle-downtime","operation_ids":["operate.device-troubleshooting-power-off.show"]},{"slug":"fuel-level-monitoring","label":"Fuel Level Monitoring","description":"See fuel level percentage, volume, and tank capacity for every vehicle. Get critical level alerts, track consumption over time with daily and weekly fuel reports, and detect sudden drops that indicate theft or siphoning.","tagline":"See fuel level, volume, and consumption for every vehicle","category":"monitor","category_label":"Driver Safety","minimum_plan":"premium","availability":["available","requires_feature","requires_permission"],"setup_steps":[{"title":"Enable the feature","body":"Turn on Fuel Level Monitoring for the vehicles and team members that need this workflow."},{"title":"Review the first signals","body":"Check the first trips, alerts, or reports to confirm the data matches how your fleet operates."},{"title":"Turn data into action","body":"Use the saved history to decide what to dispatch, investigate, maintain, or discuss with the team."}],"proof_points":["The feature is backed by GPS timestamps, vehicle history, and account-level access controls.","Managers can review the record later instead of relying on calls, memory, or screenshots."],"benefits":["Fuel level percentage and volume displayed on every trip","Critical level alerts when fuel drops below your threshold","Weekly and daily fuel reports with consumption charts","Average, max, and min fuel levels at a glance","Detect sudden drops that indicate theft or siphoning","Tank capacity tracking for trucks, generators, and cisterns"],"use_cases":["Fleet managers monitoring fuel levels across trucks and cisterns","Generator operators getting critical level alerts before running dry","Logistics firms analyzing weekly fuel consumption reports per vehicle"],"internal_links":[{"label":"Compare GPS tracking plans","description":"See where Fuel Level Monitoring fits in the current Traxelio plan structure.","url":"https:\/\/traxelio.com\/pricing"},{"label":"Getting Started with Fleet Tracking","description":"Use the setup guide to turn tracker data into a working fleet dashboard.","url":"https:\/\/traxelio.com\/guides\/getting-started-fleet-tracking"},{"label":"Talk to Traxelio","description":"Ask which tracker and workflow fit your vehicles.","url":"https:\/\/traxelio.com\/lead\/new"}],"url":"https:\/\/traxelio.com\/features\/fuel-level-monitoring","operation_ids":["operate.device-fuel.store","operate.device-settings-fuel-monitoring.show","operate.device-settings-fuel-monitoring.store"]},{"slug":"driving-behaviors","label":"Driving Behaviors","description":"Analyze harsh braking, rapid acceleration, and sharp cornering to generate driver safety scores and reduce accident risks.","tagline":"Driver scoring software to reduce accidents and insurance costs","category":"monitor","category_label":"Driver Safety","minimum_plan":"premium","availability":["available","requires_feature","requires_permission"],"setup_steps":[{"title":"Choose scoring events","body":"Track speeding, harsh braking, rapid acceleration, and cornering events that match your driver policy."},{"title":"Review by vehicle or driver","body":"Compare patterns across the fleet and separate one-off incidents from repeated coaching signals."},{"title":"Coach with trip proof","body":"Open the related trip, timestamp, and location before speaking with the driver."}],"proof_points":["Each coaching conversation can point to a trip, timestamp, event type, and location.","Scores help compare repeated driving patterns without relying on passenger or customer complaints alone."],"benefits":["Automatic driver score calculated from harsh braking, acceleration, and cornering events","Detect harsh braking and sudden deceleration live","Analyze rapid acceleration patterns that increase fuel consumption","Monitor sharp turns and swerving that indicate risky driving","Compare driver scores across your fleet to identify coaching needs","Reduce accident rates and lower your insurance premiums"],"use_cases":["Fleet managers scoring and ranking drivers by safety performance","Car rental companies detecting vehicle abuse from harsh braking patterns","VTC and taxi operators coaching drivers to reduce accidents","Logistics companies lowering insurance premiums with driving data","Transport businesses ensuring regulatory compliance"],"internal_links":[{"label":"Taxi & VTC","description":"See driver scoring in a fleet where behavior and trip proof matter daily.","url":"https:\/\/traxelio.com\/use-cases\/taxi-vtc"},{"label":"Insurance & Risk Management","description":"Review how GPS evidence can support risk conversations.","url":"https:\/\/traxelio.com\/use-cases\/insurance"},{"label":"Compare driver-safety plans","description":"Check where behavior scoring sits in the current plan structure.","url":"https:\/\/traxelio.com\/pricing"}],"url":"https:\/\/traxelio.com\/features\/driving-behaviors","operation_ids":["operate.device-acceleration.index"]},{"slug":"poi-tracking","label":"POI Detection","description":"Automatically detect frequent stop locations and get notified of new patterns.","tagline":"Know where your vehicles stop most","category":"track","category_label":"Track & Monitor","minimum_plan":"premium","availability":["available","requires_feature","requires_permission"],"setup_steps":[{"title":"Enable the feature","body":"Turn on POI Detection for the vehicles and team members that need this workflow."},{"title":"Review the first signals","body":"Check the first trips, alerts, or reports to confirm the data matches how your fleet operates."},{"title":"Turn data into action","body":"Use the saved history to decide what to dispatch, investigate, maintain, or discuss with the team."}],"proof_points":["The feature is backed by GPS timestamps, vehicle history, and account-level access controls.","Managers can review the record later instead of relying on calls, memory, or screenshots."],"benefits":["Discover frequent parking and stop patterns","Detect unauthorized or unexpected stop locations","Identify customer and delivery locations automatically","Optimize routes based on actual stop data"],"use_cases":["Fleet managers analyzing vehicle stop patterns","Rental companies detecting unauthorized parking locations","Logistics teams optimizing delivery stop efficiency"],"internal_links":[{"label":"Compare GPS tracking plans","description":"See where POI Detection fits in the current Traxelio plan structure.","url":"https:\/\/traxelio.com\/pricing"},{"label":"Getting Started with Fleet Tracking","description":"Use the setup guide to turn tracker data into a working fleet dashboard.","url":"https:\/\/traxelio.com\/guides\/getting-started-fleet-tracking"},{"label":"Talk to Traxelio","description":"Ask which tracker and workflow fit your vehicles.","url":"https:\/\/traxelio.com\/lead\/new"}],"url":"https:\/\/traxelio.com\/features\/poi-tracking","operation_ids":["operate.poi.index","operate.poi.show","operate.device-poi.index"]},{"slug":"evidence-packs","label":"Driver Evidence Packs","description":"Generate timestamped, exportable dossiers for any incident. Combine trip replay, speed graphs, braking events, and location data into a single report to settle disputes, support insurance claims, or document driver misconduct.","tagline":"Settle any dispute with timestamped, exportable proof","category":"monitor","category_label":"Driver Safety","minimum_plan":"premium","availability":["available","requires_feature","requires_permission"],"setup_steps":[{"title":"Enable the feature","body":"Turn on Driver Evidence Packs for the vehicles and team members that need this workflow."},{"title":"Review the first signals","body":"Check the first trips, alerts, or reports to confirm the data matches how your fleet operates."},{"title":"Turn data into action","body":"Use the saved history to decide what to dispatch, investigate, maintain, or discuss with the team."}],"proof_points":["The feature is backed by GPS timestamps, vehicle history, and account-level access controls.","Managers can review the record later instead of relying on calls, memory, or screenshots."],"benefits":["Timestamped incident dossiers with trip replay and speed data","Exportable PDF reports for disputes, claims, and HR proceedings","Combine location, speed, braking, and impact data in one report","Protect against false claims from drivers or third parties","Instant evidence generation for any date range or incident","Admissible documentation for insurance and legal disputes"],"use_cases":["Fleet managers documenting grounds for driver termination","Car rental companies proving renter fault for vehicle damage","Taxi and VTC operators resolving passenger complaints with data","Insurance teams building claims with GPS-backed evidence"],"internal_links":[{"label":"Compare GPS tracking plans","description":"See where Driver Evidence Packs fits in the current Traxelio plan structure.","url":"https:\/\/traxelio.com\/pricing"},{"label":"Getting Started with Fleet Tracking","description":"Use the setup guide to turn tracker data into a working fleet dashboard.","url":"https:\/\/traxelio.com\/guides\/getting-started-fleet-tracking"},{"label":"Talk to Traxelio","description":"Ask which tracker and workflow fit your vehicles.","url":"https:\/\/traxelio.com\/lead\/new"}],"url":"https:\/\/traxelio.com\/features\/evidence-packs","operation_ids":[]},{"slug":"ai-chatbot","label":"AI Chatbot","description":"Smart assistant available via SMS, WhatsApp, web chat, and email. Ask questions, place orders, activate devices, schedule installations, get quotes, and search our knowledge base in French, English, Wolof, or Spanish.","tagline":"Get answers, place orders, and manage your fleet by chat","category":"manage","category_label":"Manage & Optimize","minimum_plan":"basic","availability":["available","requires_permission"],"setup_steps":[{"title":"Enable the feature","body":"Turn on AI Chatbot for the vehicles and team members that need this workflow."},{"title":"Review the first signals","body":"Check the first trips, alerts, or reports to confirm the data matches how your fleet operates."},{"title":"Turn data into action","body":"Use the saved history to decide what to dispatch, investigate, maintain, or discuss with the team."}],"proof_points":["The feature is backed by GPS timestamps, vehicle history, and account-level access controls.","Managers can review the record later instead of relying on calls, memory, or screenshots."],"benefits":["Available via SMS, WhatsApp, web chat, and email","Place orders and get personalized quotes through conversation","Check device status, subscriptions, and order history instantly","Schedule GPS installations with live technician availability","Activate your own GPS tracker without calling support","Supports French, English, Wolof, and Spanish"],"use_cases":["New customers asking product and pricing questions before buying","Existing customers checking device status or order updates via SMS","Fleet managers requesting quotes for bulk GPS tracker purchases","Customers scheduling installation appointments without calling"],"internal_links":[{"label":"Compare GPS tracking plans","description":"See where AI Chatbot fits in the current Traxelio plan structure.","url":"https:\/\/traxelio.com\/pricing"},{"label":"Getting Started with Fleet Tracking","description":"Use the setup guide to turn tracker data into a working fleet dashboard.","url":"https:\/\/traxelio.com\/guides\/getting-started-fleet-tracking"},{"label":"Talk to Traxelio","description":"Ask which tracker and workflow fit your vehicles.","url":"https:\/\/traxelio.com\/lead\/new"}],"url":"https:\/\/traxelio.com\/features\/ai-chatbot","operation_ids":["support.chatbot.store","support.chat-message.store","support.chat-conversations.index","support.chat-conversations.store"]}]},"components":{"examples":{"ProductListResponse":{"summary":"One page of published GPS tracker products, priced in USD","value":{"data":[{"id":"01J8Z3F6R8Q6ZC4V6Y9N2K3H7T","name":"Traxelio GL300 GPS Tracker","slug":"traxelio-gl300-gps-tracker","model":"GL300","currency":"USD","price":4999,"price_text":"$49.99","medias":["https:\/\/cdn.traxelio.com\/products\/gl300-1.jpg"],"feature_media_url":"https:\/\/cdn.traxelio.com\/products\/gl300-1.jpg","has_addons":true,"categories":["car"],"description":"Compact wired GPS tracker with real-time location and remote engine cut.","rating":4.6,"installs_this_month":128}],"links":{"first":"https:\/\/traxelio.com\/api\/product?page=1","last":"https:\/\/traxelio.com\/api\/product?page=3","prev":null,"next":"https:\/\/traxelio.com\/api\/product?page=2"},"meta":{"current_page":1,"per_page":15,"total":42,"currency":"USD"}}},"CartItemStoreRequest":{"summary":"Add a GPS tracker to the cart","value":{"product_id":"01J8Z3F6R8Q6ZC4V6Y9N2K3H7T","addons":[],"device_ulids":[]}},"CartResponse":{"summary":"Cart with one product line item","value":{"data":{"id":"01J8Z400H5X3PXR3E2Q8D0J5MB","sub_total":4999,"sub_total_text":"$49.99","currency":"USD","store_whatsapp_phone":null,"shipping_zones":[],"items":[{"id":"01J8Z407T3S9V4E7R3M2K1N5CQ","bundle_id":null,"type":"product","label":"Traxelio GL300 GPS Tracker","price":4999,"price_text":"$49.99","currency":"USD","quantity":1,"product":{"id":"01J8Z3F6R8Q6ZC4V6Y9N2K3H7T","name":"Traxelio GL300 GPS Tracker","image_url":"https:\/\/cdn.traxelio.com\/products\/gl300-1.jpg"}}]}}},"OrderCheckoutRequest":{"summary":"Checkout with a saved shipping zone","value":{"payment_method":"stripe","shipping_zone":"dakar-metro","success_url":"https:\/\/app.traxelio.com\/orders\/success"}},"OrderCheckoutResponse":{"summary":"Pending order + gateway checkout URL","value":{"data":{"order_id":"01J8Z40Q7N6C1V2K9T4M8H3R5X","payment_id":"01J8Z40Y0B2E5W7Q1D9F6M4K8Z","checkout_url":"https:\/\/checkout.stripe.com\/c\/pay\/cs_test_a1b2c3"}}},"DeviceSetupRequest":{"summary":"Bind a tracker's SIM phone and hardware ID to a device","value":{"phone":"+221771234567","unique_id":"868712345678901"}},"DeviceSetupResponse":{"summary":"Device after setup, pending its first GPS fix","value":{"data":{"id":"01J8Z3ABCDEF1234567890GHJK","ulid":"01J8Z3ABCDEF1234567890GHJK","name":"Delivery Van 1","model":"GL300","disabled":false,"setup_at":"2026-07-25T10:00:00Z","unique_id_redacted":"\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u20228901","phone_redacted":"\u2022\u2022\u2022\u2022\u2022\u2022\u20224567"}}},"DeviceSubscriptionRequest":{"summary":"Subscribe a set-up device to the premium plan for one year","value":{"plan":"premium","payment_method":"wave","duration":"one_year"}},"DeviceSubscriptionResponse":{"summary":"Pending payment + gateway checkout URL","value":{"data":{"checkout_url":"https:\/\/pay.wave.com\/c\/cs_test_a1b2c3","payment_id":"01J8Z40Y0B2E5W7Q1D9F6M4K8Z"}}},"DeviceListResponse":{"summary":"One page of the caller's fleet","value":{"data":[{"id":"01J8Z3ABCDEF1234567890GHJK","ulid":"01J8Z3ABCDEF1234567890GHJK","name":"Delivery Van 1","disabled":false,"ignition_on":true,"battery_level":92.5,"fuel_level_percentage":64,"position":{"latitude":14.6928,"longitude":-17.4467,"speed":42.1}}],"links":{"first":"https:\/\/traxelio.com\/api\/device?page=1","last":"https:\/\/traxelio.com\/api\/device?page=1","prev":null,"next":null},"meta":{"current_page":1,"per_page":50,"total":1}}},"DevicePositionListResponse":{"summary":"Recent GPS fixes for one device","value":{"data":[{"id":987654,"created_at":"2026-07-25T09:58:00Z","latitude":14.6928,"longitude":-17.4467,"speed":42.1,"speed_text":"42 km\/h","address":"Route de Ouakam, Dakar"}],"links":{"first":"https:\/\/traxelio.com\/api\/device\/01J8Z3ABCDEF1234567890GHJK\/position?page=1","last":null,"prev":null,"next":null},"meta":{"current_page":1,"per_page":30,"total":1}}},"DeviceCommandRequest":{"summary":"Remotely cut the engine","value":{"command":"engineStop"}},"DeviceCommandResponse":{"summary":"Command accepted and delivered to the tracker","value":{"status":"delivered","message":"Command sent to device.","delivered_via":"ingestor"}},"UserMeResponse":{"summary":"Signed-in caller's own profile","value":{"data":{"uuid":"01J8Z3ABCDEF1234567890AAAA","ulid":"01J8Z3ABCDEF1234567890AAAA","name":"Fatou Ndiaye","email":"fatou@example.com","readonly":false,"has_password":true,"is_technician":false,"locale":"fr","currency":"XOF","subscriptions":[],"feature_flags":[]},"app":{"support_phone":"+221338600000","support_email":"support@traxelio.com","min_version":"0.70.0","latest_version":"0.76.22"}}},"ChatbotRequest":{"summary":"Guest asking about pricing","value":{"message":"How much does the GL300 tracker cost with a 1-year plan?"}},"ChatbotResponse":{"summary":"Assistant reply with a new session started","value":{"reply":"The GL300 starts at $49.99 for the tracker, plus a plan from $9.99\/month \u2014 want me to walk you through the options?","session_id":"3fa85f64-5717-4562-b3fc-2c963f66afa6"}}},"schemas":{"Device":{"type":"object","description":"fuel_level, fuel_level_percentage, and fuel_level_liters are null unless the device's attributes.can_see_fuel_level is true. Returned as-is (full shape) by every endpoint that embeds a device \u2014 list endpoints do not truncate it.","properties":{"id":{"type":"string","description":"Device ULID (same as ulid)."},"ulid":{"type":"string"},"name":{"type":"string"},"model":{"type":["string","null"]},"protocol":{"type":["string","null"]},"protocol_meta":{"type":["object","null"],"description":"Null when protocol is null; otherwise always present.","properties":{"label":{"type":"string"},"port":{"type":["integer","null"]}},"additionalProperties":false},"unique_id":{"type":["string","null"],"description":"Sensitive: raw tracker hardware\/SIM identifier. Prefer unique_id_redacted for display."},"unique_id_redacted":{"type":["string","null"]},"last_updated":{"type":["string","null"],"format":"date-time"},"phone":{"type":["string","null"],"description":"Sensitive: raw bound phone number. Prefer phone_redacted for display."},"phone_redacted":{"type":["string","null"]},"disabled":{"type":"boolean"},"ignition_on":{"type":["boolean","null"]},"category":{"type":["string","null"]},"position_id":{"type":["integer","null"],"description":"Internal numeric position ID, not exposed elsewhere as a fetchable resource ID."},"setup_at":{"type":["string","null"],"format":"date-time"},"total_distance":{"type":["number","null"]},"total_engine_hours":{"type":["number","null"]},"battery_level":{"type":["number","null"]},"fuel_level":{"type":["number","null"]},"fuel_level_last_valid":{"type":["number","null"]},"fuel_level_is_stale":{"type":"boolean"},"fuel_unit":{"type":["string","null"]},"fuel_level_percentage":{"type":["number","null"]},"fuel_level_liters":{"type":["number","null"]},"fuel_sensor_type":{"type":["string","null"]},"fuel_sensor_type_meta":{"type":["object","null"],"description":"Null when fuel_sensor_type is null or not a recognized FuelSensorTypeEnum value; otherwise always present.","properties":{"label":{"type":"string"},"description":{"type":"string"}},"additionalProperties":false},"airtime_expires_at":{"type":["string","null"],"format":"date-time"},"attributes":{"type":"object","description":"Raw attributes bag; internal gps_stall_* keys stripped.","additionalProperties":true},"vehicle_make":{"type":["string","null"]},"vehicle_model":{"type":["string","null"]},"plate_number":{"type":["string","null"]},"fuel_type":{"type":["string","null"]},"seats_count":{"type":["integer","null"]},"horsepower":{"type":["integer","null"]},"engine_displacement":{"type":["integer","null"]},"fuel_tank_capacity":{"type":["number","null"]},"gps_signal":{"type":"object","properties":{"status":{"type":"string","description":"\"healthy\", or a stall-state value."},"position_stale":{"type":"boolean"},"stall_since":{"type":["string","null"],"format":"date-time"},"last_real_movement_at":{"type":["string","null"],"format":"date-time"},"last_real_fix":{"type":["object","null"],"additionalProperties":true},"last_real_fix_place":{"type":["string","null"]},"reason":{"type":["string","null"]},"monitored":{"type":"boolean"},"can_book_maintenance":{"type":"boolean"}},"additionalProperties":false},"features":{"type":"object","properties":{"can_see_fuel_level":{"type":["boolean","null"]},"can_arm_alarm":{"type":["boolean","null"]},"can_power_off":{"type":["boolean","null"]},"can_see_cash_flow":{"type":["boolean","null"]}},"additionalProperties":false},"team":{"type":"object","description":"Key is absent unless the team relation is eager-loaded (device.setup and device.show both load it; device.index does not).","additionalProperties":true},"position":{"type":["object","null"],"description":"Latest valid position, or null when the device has none.","additionalProperties":true},"subscription":{"type":"object","description":"Key is absent unless the subscription relation is eager-loaded (only on show).","additionalProperties":true},"camera_device":{"type":"object","description":"Present only when the device has a linked camera and the cameraDevice relation is eager-loaded (only on show); omitted entirely otherwise.","additionalProperties":true}},"required":["id","ulid","name","disabled"],"additionalProperties":false},"DeviceDocument":{"type":"object","properties":{"id":{"type":"string","description":"ULID."},"name":{"type":["string","null"]},"expires_at":{"type":["string","null"],"format":"date-time"},"device":{"$ref":"#\/components\/schemas\/Device","description":"Full device shape (see \/api\/device\/{device}). Key is absent unless the relation is eager-loaded."},"device_id":{"type":"string","description":"Device ULID (owning device's route key)."},"medias":{"type":"array","items":{"type":"object","properties":{"name":{"type":"string"},"url":{"type":"string","format":"uri","description":"Temporary signed URL, ~5 minute expiry."}},"required":["name","url"],"additionalProperties":false}}},"required":["id","device_id"],"additionalProperties":false},"DeviceInspection":{"type":"object","properties":{"id":{"type":"string","description":"ULID."},"inspection_date":{"type":["string","null"],"format":"date-time"},"notes":{"type":"string"},"device":{"$ref":"#\/components\/schemas\/Device","description":"Full device shape (see \/api\/device\/{device}). Key is absent unless the relation is eager-loaded."},"inspector":{"type":"object","description":"Public inspector profile fields. Key is absent unless the relation is eager-loaded.","additionalProperties":true},"answers":{"type":"array","items":{"type":"object","additionalProperties":true}},"medias":{"type":"array","items":{"type":"string","format":"uri"}}},"required":["id","notes"],"additionalProperties":false},"DeviceIssue":{"type":"object","properties":{"id":{"type":"string","description":"ULID."},"device_id":{"type":"integer","description":"Device's internal numeric ID (not the ULID)."},"device":{"$ref":"#\/components\/schemas\/Device","description":"Full device shape (see \/api\/device\/{device}). Key is absent unless the relation is eager-loaded."}},"required":["id","device_id"],"additionalProperties":false},"DeviceService":{"type":"object","properties":{"id":{"type":"string","description":"ULID."},"device":{"$ref":"#\/components\/schemas\/Device","description":"Full device shape (see \/api\/device\/{device}). Key is absent unless the relation is eager-loaded."}},"required":["id"],"additionalProperties":false},"Geofence":{"type":"object","description":"BREAKING (2026-07-27): latitude\/longitude\/radius\/points used to be strings substring-parsed out of the raw WKT area and never cast \u2014 they are now numbers. A client that parsed these fields as strings will now receive a JSON number instead. latitude\/longitude\/radius are present when type is circle; points is present when type is polygon. Used for both account-level geofences (\/api\/geofence) and device geo-zones (\/api\/device\/{device}\/geo-zone) \u2014 identical Geofence model, different scoping.","properties":{"id":{"type":"integer"},"name":{"type":"string"},"area":{"type":"string","description":"Raw WKT geometry string, e.g. \"CIRCLE(lat lon, radius)\" or \"POLYGON((...))\". LINESTRING is also a valid stored value (e.g. legacy routes) though writes through this API only ever produce CIRCLE\/POLYGON."},"description":{"type":["string","null"]},"type":{"type":"string","description":"GeofenceAreaTypeEnum: \"circle\" or \"polygon\".","enum":["circle","polygon"]},"attributes":{"type":"object","description":"Includes alerts_enabled, source (auto\/manual), color, customer_edited, and other free-form keys.","additionalProperties":true},"latitude":{"type":"number","description":"Parsed out of the area WKT and cast to a float \u2014 e.g. 14.6928."},"longitude":{"type":"number","description":"Parsed out of the area WKT and cast to a float."},"radius":{"type":"number","description":"Meters, parsed out of the area WKT and cast to a float."},"points":{"type":"array","items":{"type":"object","properties":{"latitude":{"type":"number","description":"Parsed out of the area WKT and cast to a float."},"longitude":{"type":"number","description":"Parsed out of the area WKT and cast to a float."}},"required":["latitude","longitude"],"additionalProperties":false}}},"required":["id","name","area","type"],"additionalProperties":false},"Position":{"type":"object","description":"speed is in km\/h (speed_text is the pre-formatted display string).","properties":{"id":{"type":"integer"},"created_at":{"type":["string","null"],"format":"date-time","description":"servertime."},"latitude":{"type":"number"},"longitude":{"type":"number"},"speed":{"type":"number"},"speed_text":{"type":"string"},"course":{"type":["number","null"]},"fuel_level":{"type":["number","null"]},"battery_level":{"type":["number","null"]},"address":{"type":["string","null"]},"device":{"$ref":"#\/components\/schemas\/Device","description":"Full device shape (see \/api\/device\/{device}). Key is absent unless the relation is eager-loaded."}},"required":["id","latitude","longitude","speed"],"additionalProperties":false},"Event":{"type":"object","properties":{"id":{"type":"integer"},"type":{"type":"string","description":"Event type, e.g. \"alarm\", \"deviceOverspeed\", \"geofenceEnter\"."},"label":{"type":"string"},"description":{"type":["string","null"]},"eventtime":{"type":"string","format":"date-time"},"positionid":{"type":["integer","null"]},"position":{"type":["object","null"],"description":"Always present (index\/show both eager-load the position relation). Null when positionid is null (no linked position), an empty object when the linked position record failed to resolve, otherwise the position fields.","additionalProperties":true},"geofenceid":{"type":["integer","null"]},"geofence":{"type":["object","null"],"description":"Always present (index\/show both eager-load the geofence relation). Null when geofenceid is null (no linked geofence), otherwise the geofence fields.","additionalProperties":true},"attributes":{"type":"object","description":"Type-specific payload, e.g. {alarm: \"fuelLeak\"}.","additionalProperties":true}},"required":["id","type","eventtime","positionid","position","geofenceid","geofence"],"additionalProperties":false},"Poi":{"type":"object","properties":{"id":{"type":"string","description":"ULID."},"name":{"type":"string"},"latitude":{"type":"number"},"longitude":{"type":"number"},"address":{"type":["string","null"]},"total_duration":{"type":"integer","description":"Seconds; 0 unless visits are eager-loaded."},"visit_count":{"type":"integer","description":"0 unless visits are eager-loaded."},"device_count":{"type":"integer","description":"0 unless visits are eager-loaded."},"geofence_id":{"type":["integer","null"],"description":"Linked auto-promoted geofence, or null when the POI has not been promoted to a geofence."},"visits":{"type":"array","items":{"type":"object","additionalProperties":true}},"computed_at":{"type":"string","format":"date-time"}},"required":["id","name","latitude","longitude","computed_at"],"additionalProperties":false},"PoiVisit":{"type":"object","properties":{"id":{"type":"string","description":"ULID."},"latitude":{"type":["number","null"]},"longitude":{"type":["number","null"]},"duration":{"type":"integer","description":"Seconds."},"stopped_at":{"type":"string","format":"date-time"}},"required":["id","duration","stopped_at"],"additionalProperties":false},"Product":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"slug":{"type":"string"},"model":{"type":["string","null"]},"currency":{"type":"string"},"price":{"$ref":"#\/components\/schemas\/MoneyAmountMinor"},"price_text":{"type":"string"},"medias":{"type":"array","items":{"type":"string"}},"feature_media_url":{"type":["string","null"]},"has_addons":{"type":"boolean"},"categories":{"type":"array","items":{"type":"string"}},"description":{"type":["string","null"]},"rating":{"type":["number","null"]},"installs_this_month":{"type":"integer"}},"additionalProperties":false},"GeozoneTemplate":{"type":"object","properties":{"id":{"type":"string","description":"GeozoneTemplate ULID."},"name":{"type":"string"},"slug":{"type":"string"},"type":{"type":"string"},"country_code":{"type":"string"},"parent_slug":{"type":["string","null"]},"center_lat":{"type":"number"},"center_lng":{"type":"number"},"price":{"type":"integer"},"price_text":{"type":"string"},"currency":{"type":"string"},"polygon":{"type":"object","additionalProperties":true},"is_owned":{"type":"boolean"}},"additionalProperties":false},"Cart":{"type":"object","properties":{"id":{"type":"string"},"sub_total":{"$ref":"#\/components\/schemas\/MoneyAmountMinor"},"sub_total_text":{"type":"string"},"currency":{"type":["string","null"]},"store_whatsapp_phone":{"type":["string","null"]},"shipping_zones":{"type":"array","items":{"type":"object","properties":{"zone":{"type":"string"},"country":{"type":"string"},"region":{"type":["string","null"]},"shipping_flat_rate":{"$ref":"#\/components\/schemas\/MoneyAmountMinor"},"shipping_min_days":{"type":"integer"},"shipping_max_days":{"type":"integer"}},"additionalProperties":false}},"items":{"type":"array","items":{"$ref":"#\/components\/schemas\/CartItem"}}},"additionalProperties":false},"CartItem":{"type":"object","properties":{"id":{"type":"string"},"bundle_id":{"type":["string","null"]},"type":{"type":["string","null"],"description":"\"product\" or \"addon\".","enum":["product","addon"]},"label":{"type":["string","null"]},"price":{"$ref":"#\/components\/schemas\/MoneyAmountMinor"},"price_text":{"type":"string"},"currency":{"type":"string"},"quantity":{"type":"integer"},"product":{"type":["object","null"],"properties":{"id":{"type":"string","description":"Product ULID."},"name":{"type":"string"},"image_url":{"type":["string","null"]}},"additionalProperties":false}},"additionalProperties":false},"Order":{"type":"object","properties":{"id":{"type":"string"},"id_short":{"type":"string"},"buyer_name":{"type":["string","null"]},"currency":{"type":"string"},"total":{"$ref":"#\/components\/schemas\/MoneyAmountMinor"},"total_text":{"type":"string"},"paid_at":{"type":["string","null"],"format":"date-time"},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"},"items":{"type":"array","items":{"$ref":"#\/components\/schemas\/OrderItem"}},"payments":{"type":"array","items":{"$ref":"#\/components\/schemas\/Payment"}}},"required":["id","currency","total"],"additionalProperties":false},"OrderItem":{"type":"object","properties":{"id":{"type":"string"},"label":{"type":["string","null"]},"quantity":{"type":"integer"},"currency":{"type":"string"},"price":{"$ref":"#\/components\/schemas\/MoneyAmountMinor"},"price_text":{"type":"string"},"total":{"$ref":"#\/components\/schemas\/MoneyAmountMinor"},"total_text":{"type":"string"},"product_id":{"type":["string","null"]},"product_type":{"type":["string","null"],"description":"Morph alias \u2014 one of: product, subscription_plan, geozone_template (varies by order line-item type)."}},"additionalProperties":false},"Payment":{"type":"object","properties":{"id":{"type":"string"},"id_short":{"type":"string"},"status":{"type":"string","description":"PaymentStatusEnum value."},"status_text":{"type":"string"},"method":{"type":"string","description":"PaymentMethodEnum value."},"method_text":{"type":"string"},"amount_text":{"type":"string"},"buyer_name":{"type":["string","null"]},"buyer_id":{"type":["string","null"],"description":"Sensitive identifier."},"buyer_type":{"type":["string","null"],"description":"Morph alias, always \"user\" for orders\/payments created via Convert endpoints."},"product_id":{"type":["string","null"]},"product_type":{"type":["string","null"],"description":"Morph alias \u2014 one of: product, subscription_plan, geozone_template (varies by order line-item type)."},"failure_reason_code":{"type":["string","null"],"description":"Provider-specific free-form failure code (Wave\/Stripe\/Orange Money error taxonomy) \u2014 not a fixed enum."},"failure_reason_message":{"type":["string","null"],"description":"Provider-specific free-form failure message."},"failure_reason_at":{"type":["string","null"],"format":"date-time"},"created_at":{"type":"string","format":"date-time"}},"required":["id","status","method"],"additionalProperties":false},"Transaction":{"type":"object","properties":{"id":{"type":"string","description":"ULID."},"payment_id":{"type":["string","null"]},"label":{"type":"string"},"description":{"type":["string","null"]},"amount_text":{"type":"string","description":"Formatted amount, e.g. \"$120.00\"."},"amount":{"type":"number","description":"Signed \u2014 positive is revenue, negative is expense. BREAKING (2026-07-27): this used to be an integer truncated to whole major units (12345 minor units rendered as 123, silently dropping the .45). It is now the exact major-unit decimal (123.45), rounded to 2 places, matching the float already accepted on write. A client that parsed this field as an integer will now see a decimal point. Pass ?amount_unit=subunit to instead receive the exact, untruncated minor-unit integer \u2014 unaffected by this change."},"currency":{"type":"string","description":"ISO currency code."},"created_at":{"type":"string","format":"date-time"},"can_edit":{"type":"boolean"},"can_delete":{"type":"boolean"},"media_count":{"type":"integer"},"medias":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"url":{"type":["string","null"],"format":"uri"}},"required":["id","name"],"additionalProperties":false}},"device":{"$ref":"#\/components\/schemas\/Device","description":"Full device shape (see \/api\/device\/{device}). Key is absent unless the relation is eager-loaded."}},"required":["id","label","amount_text","amount","currency","created_at","can_edit","can_delete","media_count"],"additionalProperties":false},"CheckoutResult":{"type":"object","properties":{"checkout_url":{"type":"string","format":"uri","description":"Payment gateway URL the human completes to pay."},"payment_id":{"type":"string"}},"additionalProperties":false},"User":{"type":"object","properties":{"uuid":{"type":"string","description":"Same value as ulid \u2014 kept for backward compatibility."},"ulid":{"type":"string","description":"User ULID; the route key for {user}."},"name":{"type":"string"},"email":{"type":"string","description":"Sensitive identifier."},"phone":{"type":["string","null"],"description":"Sensitive identifier."},"readonly":{"type":"boolean"},"avatar_url":{"type":["string","null"],"format":"uri"},"has_password":{"type":"boolean"},"is_admin":{"type":["boolean","null"],"description":"Present only for admin callers."},"is_technician":{"type":"boolean"},"subscriptions":{"type":"array","items":{"type":"object","description":"Subscription shape (see \/api\/subscription\/{subscription}). Always present on \/api\/user\/me and PUT\/POST \/api\/user endpoints.","additionalProperties":true}},"feature_flags":{"type":"array","items":{"type":"object","additionalProperties":true}},"locale":{"type":"string","description":"Resolved preferred locale."},"currency":{"type":"string","description":"Resolved preferred ISO currency code."}},"required":["ulid","name"],"additionalProperties":false},"Subscription":{"type":"object","properties":{"id":{"type":"string","description":"ULID."},"plan_slug":{"type":["string","null"]},"ends_at":{"type":["string","null"],"format":"date-time"},"activated_at":{"type":["string","null"],"format":"date-time"},"features":{"type":"object","properties":{"trip_history":{"type":["boolean","null"]}},"additionalProperties":false},"billing_period":{"type":["string","null"],"description":"Legacy alias for duration."},"duration":{"type":["string","null"]},"price":{"oneOf":[{"$ref":"#\/components\/schemas\/MoneyAmountMinor"},{"type":"null"}]},"currency":{"type":["string","null"]},"price_text":{"type":["string","null"]},"subscriber":{"type":"object","properties":{"name":{"type":["string","null"]}},"additionalProperties":false},"product_id":{"type":["string","null"],"description":"Subscribed device's route key (ULID)."},"plan":{"type":"object","description":"Plan detail (see \/api\/subscription\/plan). Key is absent unless the plan relation is eager-loaded \u2014 loaded by GET \/api\/subscription and GET \/api\/subscription\/{subscription}; not loaded by the user-nested subscription endpoints.","additionalProperties":true}},"required":["id"],"additionalProperties":false},"Team":{"type":"object","properties":{"ulid":{"type":"string","description":"Team ULID; the route key for {team}."},"name":{"type":"string"},"email":{"type":"string","description":"Sensitive identifier."},"phone":{"type":["string","null"],"description":"Sensitive identifier."},"is_root":{"type":"boolean"},"locale":{"type":"string"},"currency":{"type":"string"},"avatar_url":{"type":["string","null"],"format":"uri"},"parent":{"type":["object","null"],"description":"Always present: parent team summary, or null when this team has no parent.","additionalProperties":true}},"required":["ulid","name"],"additionalProperties":false},"TeamMember":{"type":"object","properties":{"ulid":{"type":"string","description":"Member's user ULID."},"name":{"type":"string"},"email":{"type":"string","description":"Sensitive identifier. Visible to fellow team members by design (team roster)."},"phone":{"type":["string","null"],"description":"Sensitive identifier."},"avatar_url":{"type":["string","null"],"format":"uri"},"permissions":{"type":["array","null"],"items":{"type":"string","description":"Permission slug (see \/api\/permission)."},"description":"Both GET and PUT \/api\/team\/{team}\/user\/{user} reload the teams pivot after any write before serializing, so this reflects the just-applied permissions on both \u2014 never stale or null on a successful response."}},"required":["ulid","name"],"additionalProperties":false},"Appointment":{"type":"object","properties":{"id":{"type":"string","description":"ULID."},"service_type":{"type":["string","null"]},"service_type_label":{"type":["string","null"]},"address":{"type":["string","null"]},"latitude":{"type":["number","null"]},"longitude":{"type":["number","null"]},"customer_notes":{"type":["string","null"]},"technician_notes":{"type":["string","null"]},"status":{"type":"string","description":"Derived.","enum":["pending","awaiting_client","awaiting_technician","confirmed","completed","cancelled"]},"status_label":{"type":"string"},"status_color":{"type":"string"},"is_confirmed":{"type":"boolean"},"is_technician_confirmed":{"type":"boolean"},"is_fully_confirmed":{"type":"boolean"},"is_completed":{"type":"boolean"},"is_cancelled":{"type":"boolean"},"is_pending":{"type":"boolean"},"scheduled_at":{"type":["string","null"],"format":"date-time"},"scheduled_date":{"type":["string","null"],"format":"date"},"scheduled_time":{"type":["string","null"],"description":"H:i formatted."},"confirmed_at":{"type":["string","null"],"format":"date-time"},"technician_confirmed_at":{"type":["string","null"],"format":"date-time"},"serviced_at":{"type":["string","null"],"format":"date-time"},"cancelled_at":{"type":["string","null"],"format":"date-time"},"cancellation_reason":{"type":["string","null"]},"checklist":{"type":["array","null"],"items":{"type":"object","properties":{"item":{"type":"string"},"passed":{"type":"boolean"}},"required":["item","passed"],"additionalProperties":false}},"created_at":{"type":["string","null"],"format":"date-time"},"updated_at":{"type":["string","null"],"format":"date-time"},"client_id":{"type":["integer","null"]},"client_type":{"type":["string","null"],"description":"Morph alias."},"device_ulid":{"type":["string","null"]},"technician_ulid":{"type":["string","null"]},"order_id":{"type":["string","null"]},"client":{"type":["object","null"],"description":"Key is absent unless the client relation is eager-loaded \u2014 loaded by index\/show\/store\/update; not loaded by POST \/api\/appointment\/{appointment}\/complete.","properties":{"id":{"type":["string","null"]},"type":{"type":["string","null"]},"name":{"type":["string","null"]},"email":{"type":["string","null"],"description":"Sensitive identifier."},"phone":{"type":["string","null"],"description":"Sensitive identifier."}},"additionalProperties":true},"technician":{"type":["object","null"],"description":"TechnicianResource shape (see \/api\/technician), or null when no technician is assigned. Always present (see note above).","additionalProperties":true},"device":{"description":"Or null if the device record is missing. Always present as a key (see note above).","oneOf":[{"$ref":"#\/components\/schemas\/Device"},{"type":"null"}]},"order":{"type":["object","null"],"description":"Key is absent unless the order relation is eager-loaded \u2014 loaded by show\/store\/update; not loaded by index or by POST \/api\/appointment\/{appointment}\/complete.","additionalProperties":true}},"required":["id","status"],"additionalProperties":false},"AppointmentRequest":{"type":"object","properties":{"service_type":{"type":"string","description":"ServiceTypeEnum value."},"device_ulid":{"type":"string"},"technician_ulid":{"type":["string","null"]},"scheduled_at":{"type":"string","format":"date-time","description":"Must be in the future for non-admin callers."},"address":{"type":["string","null"]},"latitude":{"type":["number","null"]},"longitude":{"type":["number","null"]},"customer_notes":{"type":["string","null"]}},"required":["service_type","device_ulid","scheduled_at"],"additionalProperties":false},"ChatConversation":{"type":"object","properties":{"id":{"type":"string"},"channel":{"type":"string","description":"\"app\" for in-app conversations."},"messages":{"type":"array","items":{"type":"object","properties":{"role":{"type":"string","description":"\"user\" or \"assistant\" \u2014 system\/tool messages are filtered out."},"content":{"type":"string"},"timestamp":{"type":["string","null"],"format":"date-time"}},"required":["role","content"],"additionalProperties":false}},"handed_off":{"type":"boolean"},"taken_over":{"type":"boolean","description":"True once a human agent has actually taken over (distinct from handed_off, which means \"awaiting agent\")."},"created_at":{"type":["string","null"],"format":"date-time"},"updated_at":{"type":["string","null"],"format":"date-time"}},"required":["id","channel","handed_off","taken_over"],"additionalProperties":false},"MoneyAmountMinor":{"type":"integer","description":"Amount in the currency's minor unit (cents). Use the matching *_text field for display."},"PaginationLinks":{"type":"object","description":"Laravel pagination link URLs (first, last, prev, next).","additionalProperties":true},"PaginationMeta":{"type":"object","description":"Laravel pagination metadata (current_page, per_page, total, ...).","additionalProperties":true},"ErrorMessage":{"type":"object","properties":{"message":{"type":"string","description":"Human-readable error message."}},"required":["message"],"additionalProperties":false},"ValidationErrorResponse":{"type":"object","properties":{"message":{"type":"string","description":"Summary message, e.g. \"The given data was invalid.\""},"errors":{"type":"object","description":"Keyed by field name; each value is a list of failing-rule messages for that field.","additionalProperties":{"type":"array","items":{"type":"string"}}}},"required":["message","errors"],"additionalProperties":false}},"responses":{"Unauthorized":{"description":"Missing, invalid, or expired bearer token.","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorMessage"}}}},"Forbidden":{"description":"The caller is authenticated but not authorized for this resource.","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorMessage"}}}},"NotFound":{"description":"The resource does not exist, or does not belong to the caller.","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorMessage"}}}},"ValidationError":{"description":"Request failed validation.","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ValidationErrorResponse"}}}},"ServerError":{"description":"The request was well-formed but could not be completed. Safe to retry.","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorMessage"}}}},"TooManyRequests":{"description":"Rate limit or plan-quota (consumable allowance) exceeded.","headers":{"Retry-After":{"description":"Seconds to wait before retrying. Present on rate-limiter-enforced 429s (Laravel's throttle middleware); not always present on plan-quota (consumable allowance) 429s.","required":false,"schema":{"type":"integer"}}},"content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorMessage"}}}}},"securitySchemes":{"sanctum":{"type":"http","scheme":"bearer"},"oauth2":{"type":"oauth2","flows":{"authorizationCode":{"authorizationUrl":"https:\/\/traxelio.com\/oauth\/authorize","tokenUrl":"https:\/\/traxelio.com\/oauth\/token","refreshUrl":"https:\/\/traxelio.com\/oauth\/token","scopes":{"user.view":"See your name, phone, and email on file.","user.update":"Change your name, phone, or email, and update your app preferences.","cart.update":"See what's in your cart and add, change, or remove items.","order.view":"See your order details and their progress.","order.create":"Start a new order or add an add-on to an existing one.","order.checkout":"Create a payment and get a checkout link for an order.","appointment.view":"See your scheduled service appointments.","appointment.create":"Schedule a new service appointment.","appointment.update":"Reschedule or mark an appointment complete.","appointment.delete":"Cancel a scheduled appointment.","device.subscription.update":"Create, renew, or upgrade a device's subscription plan.","tracking.read":"See where your vehicles are, their trip history, and recent activity.","device.alarm":"Turn a vehicle's anti-theft alarm on or off.","device.immobilize":"Remotely cut or restore a vehicle's engine power.","device.locate":"Request a single position fix, or stop a device from reporting its location.","report.read":"See mileage, odometer, and activity reports for your fleet.","geofence.read":"See your geofences and points of interest.","geofence.write":"Create, change, or delete geofences and points of interest.","maintenance.read":"See inspections, documents, service history, issues, and costs.","maintenance.write":"Log inspections, service visits, documents, issues, and costs, or remove them.","device.settings.read":"See alert thresholds like speed limit, towing, impact, low battery, and fuel monitoring.","device.settings.write":"Change or remove alert thresholds like speed limit, towing, impact, low battery, and fuel monitoring.","device.activate":"Bind a tracker to your account and select its plan.","device.destroy":"Detach a device from your account. Its history and subscription stay intact \u2014 only your ownership link is removed.","subscription.view":"See subscriptions, orders, payments, and devices tied to your account.","team.view":"See your team's members and the devices assigned to it.","team.manage":"Add, remove, or change team members and their device assignments.","notification.view":"See which devices are registered to receive push notifications.","notification.manage":"Register or remove a device for push notifications, and mark alerts as read.","support.read":"See your chat conversations with support.","support.write":"Send chat messages and start or delete support conversations.","session.revoke":"End one of your active app sessions."}}}}}},"tags":[{"name":"Authentication","description":"Token issuance \u2014 password, magic-code email, and Google\/Apple ID-token exchange"},{"name":"Discover","description":"Public catalog, pricing & reference data \u2014 browse before signing up"},{"name":"Convert","description":"Cart \u2192 order \u2192 payment \u2014 the authenticated checkout journey"},{"name":"Activate","description":"Device onboarding \u2014 bind, configure, and subscribe a GPS tracker"},{"name":"Operate: Tracking","description":"Fleet list, live position, trips, events, commands, and diagnostics"},{"name":"Operate: Reports","description":"Odometer, distance, mileage, time-series reports, and fuel logging"},{"name":"Operate: Geofences","description":"Geofence, device geo-zone, and POI (point of interest) CRUD"},{"name":"Operate: Maintenance","description":"Inspections, documents, issues, and cost\/revenue transactions"},{"name":"Operate: Settings","description":"Device alert\/behavior settings \u2014 highest-value kinds only (see section note)"},{"name":"Account","description":"Profile, sessions, and per-user currency\/locale preferences"},{"name":"Subscriptions","description":"Plan subscriptions, renewal, and pricing"},{"name":"Teams","description":"Team CRUD and team-member management"},{"name":"Notifications","description":"Expo push token registration and notification read-state"},{"name":"Support","description":"AI assistant chat (pre-auth and in-app) and lead qualification"},{"name":"Appointments","description":"Technician appointment scheduling and completion"}]}