Skip to content

Instantly share code, notes, and snippets.

@nledenyi
Created April 24, 2026 22:02
Show Gist options
  • Select an option

  • Save nledenyi/239ee99cfb171bc57a5027bb270a322a to your computer and use it in GitHub Desktop.

Select an option

Save nledenyi/239ee99cfb171bc57a5027bb270a322a to your computer and use it in GitHub Desktop.
Toyota EU: /v1/global/remote/status is a cache endpoint, and that changes everything

Toyota EU: /v1/global/remote/status is a cache endpoint, and that changes everything

Sharing a root-cause finding from an investigation on 2026-04-24. Not a fix PR yet. Posting because this appears to unify at least 10 open issues across pytoyoda/pytoyoda and pytoyoda/ha_toyota, including the predecessor DurgNomis-drol repos.

TL;DR

GET /v1/global/remote/status is a cache read. The cache is populated when the car's cellular modem transmits. That only happens during active trips (assumed auto-report from the car, to be validated), or when someone issues POST /v1/global/remote/refresh-status to command the car to wake up and transmit, for example whenever a user opens the Toyota Android app and the app requests a fresh read. pytoyoda only implements the GET side; the official Toyota app uses both.

So the unification: it is not that different endpoints have different problems. It is that /v1/global/remote/status is a single flaky endpoint that happens to be the ONLY source of door/lock/window/hood state, and the entire integration goes unavailable when it fails to respond. Two orthogonal issues come out of this one endpoint:

  1. HTTP 429 APIGW-403 "Unauthorized": the endpoint is just flaky. Requests to it randomly fail. Retries often succeed a few seconds later. This is independent of cache state.
  2. Door/window/lock/hood sensors stuck on last-trip values: even when the endpoint does respond, what it returns is a stale cache. Without an explicit POST, the cache is whatever the car last auto-reported, which was during the previous drive. When the car is parked, nothing repopulates the cache.

The irony, and the reason this has been hard to diagnose for four years: the "flaky API" everyone has been working around is the one endpoint that, even when it works, returns data we could just as well have cached client-side. Nobody ever looked past the 429 rate because it read like a rate-limit problem; nobody noticed that the 200 responses were already stale.

Evidence

After disabling the integration for 2h to let the account cool off, ran a controlled test via a small harness calling pytoyoda.Api directly:

22:03:12 CEST  GET  /v1/global/remote/status                    -> 429 APIGW-403 "Unauthorized"
22:03:12 CEST  POST /v1/global/remote/refresh-status            -> 200 ONE-GLOBAL-RS-10000 "Request Completed Successfully"
               (body: {deviceId, deviceType:"Android", guid, vin})
               (wait 30 seconds for the car to transmit)
22:03:43 CEST  GET  /v1/global/remote/status                    -> 200  occurrence_date=20:03:27Z
               (occurrence_date is 15s after the POST: that is the car waking up, transmitting, and the backend ingesting the payload)
22:04:43 CEST  GET  /v1/global/remote/status  (+60s)            -> 200  occurrence_date=20:03:27Z (unchanged)

Later, an 18-minute-idle probe on the same VIN returned 4 x 200 out of 10 rapid attempts, all four 200s carrying the same occurrence_date from the earlier POST. The endpoint stayed flaky whether calls were 1 second apart or 6 minutes apart. When it did respond, the data it returned never advanced because nothing refreshed the cache in the meantime.

Two distinct response prefixes show where the responses come from:

  • Failure: "responseCode": "APIGW-403" - Toyota's API Gateway layer
  • Success: "responseCode": "ONE-GLOBAL-RS-10000" - Toyota's backend application layer

Why the endpoint is sometimes unavailable and sometimes not, I cannot say from this data. It's flaky. Mechanism unknown.

Earlier discussion that touched pieces of this

Several people came close without connecting everything:

  • DurgNomis-drol/mytoyota PR #302 by @GitOldGrumpy (2024, unmerged): tested all three /v1/global/remote/refresh-* endpoints and implemented Vehicle.force_update(). Never merged because mytoyota was discontinued. This is the closest earlier attempt.
  • DurgNomis-drol/mytoyota #239 by @iksuli (2023): documented using force_update() to refresh EV battery data.
  • pytoyoda/pytoyoda PR #77 (merged 2025): ported the EV-specific sibling /v1/global/remote/electric/realtime-status. Good template for a refresh_vehicle_status() method addition modeled on the same pattern.
  • ha_toyota#157 by @julesxxl and @aubreyz (2025): verbatim "If I refresh the doors status in the MyToyota app and then reload the integration, the doors status is updated." @aubreyz measured app latency at 4-8 min vs HA never updating. Closed to discussion without resolution.
  • ha_toyota#137 by @jbbandos: "got solved automatically as soon as I opened the app and refreshed the status. Seems like my car had not communicated with Toyota's servers for over 12h."

Multiple people observed that opening the app fixes HA. None named the POST /refresh-status step that the app does behind the scenes.

Issues the finding appears to explain

Current repos:

  • pytoyoda/ha_toyota#87 - 6+ users across Yaris/Corolla/Highlander/Yaris Cross with doors/hood/trunk permanently stuck "open"
  • pytoyoda/ha_toyota#120 - @alewew mentions NA version had a refresh service
  • pytoyoda/ha_toyota#137 - various "unknown" / stuck data, resolved only by opening the app
  • pytoyoda/ha_toyota#150 - explicit feature request for the (electric) refresh-status button
  • pytoyoda/ha_toyota#157 - polling-layer documentation, MyToyota-app refreshes trigger HA updates
  • pytoyoda/ha_toyota#190 - remaining charge time doesn't update
  • pytoyoda/ha_toyota#229 - Lexus 12V battery drain from auth errors on the realtime-status POST. That specific case was EV-related, but the design concern is the same for us: POST /refresh-status can wake the car and is a potential source of 12V drain if called too eagerly.
  • pytoyoda/ha_toyota#281 - "Many disconnections with Unauthorized" - the 429 rate issue
  • pytoyoda/ha_toyota#284 - "Lock/Door states never update until new trip" - the report that clarified the door-staleness half of the pattern
  • pytoyoda/pytoyoda#161 - @MHarlock reports the exact APIGW-403 Unauthorized body two days ago, currently misclassified as a login bug

Predecessor repos:

APK body shape (for anyone implementing this)

Decompiled from com.toyota.oneapp.eu 2.8.1:

public final class RefreshVehicleStatusRequest {
    @SerializedName("deviceId")   String deviceId;
    @SerializedName("deviceType") String deviceType = "Android";
    @SerializedName("guid")       String guid;
    @SerializedName("vin")        String vin;
}

Retrofit interface declaration:

@Headers({"Content-Type: application/json"})
@POST("/v1/global/remote/refresh-status")
Object v(@Body RefreshVehicleStatusRequest body, Continuation<? super BaseCommandResponse> continuation);

In my test I used the same UUID pytoyoda already generates (controller._uuid, already sent as the x-guid header) for both deviceId and guid. No device-registration precheck; POST accepted on first try.

Proposed mitigation approach

Two changes, both small:

pytoyoda: add VEHICLE_GLOBAL_REMOTE_REFRESH_STATUS_ENDPOINT + refresh_vehicle_status(vin) method in api.py + a wrapper on Vehicle. Modeled on PR #77's electric sibling.

ha_toyota: smart per-cycle strategy that minimises cellular wake-ups, since #229 already shows that aggressive wake-up calls can drain the car's 12V battery. The details:

  • Each cycle, GET /v3/telemetry first (cheap, reliable). Derive car_moving from odometer delta.
  • Maintain a client-side cache of the last successful /v1/global/remote/status response plus its occurrence_date, so subsequent cycles can reuse it without refetching an unchanged server cache.
  • Only POST /refresh-status when:
    • Car just stopped (odometer changed in the previous cycle but not this one) - to catch door-closing events as the user exits
    • User explicitly calls a new HA service toyota.refresh_vehicle_status (to be added)
    • An opt-in option auto_wake_if_idle is ON and last_post > 12h ago. Default OFF, battery-safe.
  • Only GET /v1/global/remote/status when:
    • Car is moving (cache is being populated by auto-reports)
    • Car just stopped, or we just POSTed, or our cached occurrence_date is older than some threshold
    • Otherwise: serve LockStatus from the client-side cache. Repeatedly fetching an unchanged server-side cache is wasted quota.

For a daily-driven car parked ~23h/day, this cuts /v1/global/remote/status traffic from ~240 GETs/day to ~10-15, with 0-2 POSTs. Respects Toyota's infrastructure, avoids unnecessary modem wake-ups, and keeps door/lock state fresh during and just after drives, which is when it actually changes.

What I have NOT yet validated empirically

  • That the car auto-reports lock state changes during active trips. Reasonable assumption since that is when the modem is hot, but not tested by me.
  • Exact cache TTL. I observed it unchanged long enough to be confident it holds for at least tens of minutes, probably longer.
  • Whether there is a quota on the POST /refresh-status itself. My tests fired only one POST and it succeeded; sustained use might hit a limit.

Constraints that shaped the proposed design

  • No alternative endpoint for door/lock/window/hood state exists in the EU API surface. I checked /v1/vehiclehealth/status (warning lights only), /v3/telemetry (fuel/odometer/HV-battery only), /v2/notification/history (opaque messages, not state), and scanned all APK-declared paths. /v1/global/remote/status is the sole source of LockStatus data, so we can't route around the cache mechanism by reading a different endpoint.
  • No 12V / auxiliary-battery telemetry endpoint is exposed. Toyota surfaces only EV traction-battery SoC. We therefore cannot build a reactive "don't POST if 12V is low" safeguard; the battery cost of any POST is invisible to us at call time. That is the reason the design prefers architectural minimisation (POST only when meaningfully beneficial) over adaptive throttling.

Planned next steps

Build the two changes in the coming days and soak on live HA for a day or two, then open PRs. Happy to collaborate with maintainers or anyone else who wants to help validate.


Not trying to step on anyone's toes. Genuine thanks to @CM000n for keeping these libraries alive, and to everyone whose issue comments added pieces of this puzzle. The "Toyota API is flaky" framing has been the working assumption for years, and the API is indeed flaky, but the irony is that the one endpoint that flakes is also the one that, even when it responds, only serves a stale cache unless someone wakes the car. Flagging it as cleanly as I can so whoever builds the fix can start from the right model.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment