> ## Documentation Index
> Fetch the complete documentation index at: https://docs.mirobody.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Garmin Vendor Example

> How the Garmin device-brand client maps onto the C++ vendor contract — and why most of it is deliberately a stub.

## Overview

Garmin is a real device-brand client — [`src/health/vendor/device/garmin.cpp`](https://github.com/thetahealth/mirobody/blob/main-v2/src/health/vendor/device/garmin.cpp), registered as `garmin`. It's the most instructive example of Mirobody's "implement only what's publicly confirmable, leave the rest an honest stub" rule, because Garmin's data API is **partner-gated and push-based**.

<Info>
  This is a `vendor::Vendor` implementing the shared `authorize / fetch / webhook` contract; see [Vendor System](/en/concepts/providers) for the interface.
</Info>

## What Garmin's API shape forces

| Aspect                | Reality                                                                                                          | Effect on the client                                                                                                                                             |
| --------------------- | ---------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Auth**              | OAuth 2.0 + **PKCE** (Garmin migrated off OAuth 1.0a); the flow is public                                        | `authorize_url` is a **stub** — PKCE needs a stateful `code_verifier` that a bare URL builder can't stash (that belongs in the EHR-style stateful connect layer) |
| **Data delivery**     | **Push-based**: Garmin POSTs summaries to a registered webhook; pulls are partner-gated and retain only \~7 days | `fetch` is a **stub** that explains the model rather than faking a synchronous pull                                                                              |
| **Webhook signature** | Pushes carry **no documented signature**                                                                         | `handle_webhook` is a **stub** — it can't verify authenticity from public docs                                                                                   |
| **Disconnect**        | The account-disconnect hook **is** publicly documented                                                           | `revoke` **is implemented**                                                                                                                                      |

So of the eight contract operations, Garmin ships exactly one working call — `revoke` — and three deliberate stubs (`fetch`, `authorize_url`, `handle_webhook`), with `list_providers` N/A (single brand). The OAuth token pair — `exchange_code` / `refresh` — also stays inherited stubs: Garmin's token exchange requires the persisted PKCE `code_verifier`, which belongs to the stateful connect layer, not this client. This is the intended state, recorded in the file header.

## `revoke` — the one wired operation

`revoke` calls Garmin's required account-disconnect endpoint, `DELETE /wellness-api/rest/user/registration`, authenticated with the user's OAuth 2.0 access token as a bearer:

```cpp theme={null}
void revoke(const std::string& /*user_id*/, const std::string& /*provider*/) override {
    if (config().api_key.empty()) {
        throw VendorError(info_.id + ": revoke requires a Garmin OAuth 2.0 access token — set "
                          "MIROBODY_VENDOR_GARMIN_API_KEY (partner-gated; needs approved creds)");
    }
    client::HttpRequest req;
    req.url     = base_url() + "/wellness-api/rest/user/registration";
    req.headers = { "Authorization: Bearer " + config().api_key };
    client::HttpResponse res = client::HttpClient().request("DELETE", req);
    if (res.status < 200 || res.status >= 300) {
        throw VendorError(info_.id + ": revoke failed (HTTP " +
                          std::to_string(res.status) + "): " + res.body.substr(0, 300));
    }
}
```

The data host defaults to `https://apis.garmin.com` (override via `GARMIN_BASE_URL`).

## `fetch` — a stub that explains itself

Rather than the generic "not implemented", Garmin's `fetch` throws a message stating the reason, so a caller learns the model:

```cpp theme={null}
std::string fetch(const std::string&, DataDomain,
                  const std::string&, const std::string&) override {
    throw VendorError(info_.id + ": Garmin Health is push-based and partner-gated "
                      "(OAuth2.0). Data is pushed to a registered webhook, not pulled — "
                      "wire the Ping/Pull data endpoints (partner-gated spec) or consume the "
                      "push once approved partner credentials exist.");
}
```

## Metadata (`info()`)

Even with most operations stubbed, the vendor's `VendorInfo` is fully populated and queryable without credentials:

```cpp theme={null}
i.id                 = "garmin";
i.display_name       = "Garmin Health";
i.positioning        = "Premium wearable brand; partner-gated, push-based Health API";
i.integration_method = "OAuth2.0 + PKCE; push (Ping/Push webhook) delivery; backfill triggers async";
i.docs_url           = "https://developer.garmin.com/gc-developer-program/health-api/";
i.region             = Region::Global;
i.domains            = {DataDomain::Activity, DataDomain::HeartRate,
                        DataDomain::Sleep, DataDomain::BodyMetrics};
i.integrations       = {Integration::Rest, Integration::Webhook};
```

## Configuration

Set in `config.yml` or as same-named env vars (see `config.example.yml`; the `MIROBODY_VENDOR_<ID>_*` prefix is legacy, read only by the standalone `vendor` CLI):

```bash theme={null}
# Garmin partner-gated OAuth 2.0 access token (needed for revoke; data path is partner-gated)
GARMIN_API_KEY=your_garmin_oauth2_access_token
# optional host override
GARMIN_BASE_URL=https://apis.garmin.com
```

## Takeaways for your own vendor

* **Implement what's publicly confirmable; stub the rest with a reason.** A stub that names the missing contract is better than a fabricated one.
* **A stateful OAuth flow (PKCE) doesn't fit `authorize_url` alone** — Mirobody keeps such flows in a stateful connect layer (as the SMART-on-FHIR EHR connect service does), not in the URL-builder signature.
* **`VendorInfo` should be complete regardless** — it powers selection / comparison without a network call.

For a device brand with a fuller implementation, contrast Garmin with **Fitbit** (`src/health/vendor/device/fitbit.cpp`), which implements `fetch`, `authorize_url`, `revoke`, and a signed `handle_webhook`.

## Next steps

<CardGroup cols={2}>
  <Card title="Vendor System" icon="plug" href="/en/concepts/providers">
    The contract Garmin implements
  </Card>

  <Card title="Provider Integration" icon="code" href="/en/development/provider-integration">
    Build your own `vendor::Vendor`
  </Card>

  <Card title="OAuth Implementation" icon="key" href="/en/development/oauth-implementation">
    OAuth 2.0 / PKCE patterns in the vendor clients
  </Card>

  <Card title="Provider Overview" icon="watch" href="/en/providers/overview">
    All shipped vendors
  </Card>
</CardGroup>
