# go-yoto Client

[gitlab.com/yotoshelf/go-yoto](https://gitlab.com/yotoshelf/go-yoto) is a zero-dependency Go client for the Yoto API. It handles OAuth2 PKCE and device code authentication, content library access, device management, MYO card CRUD, and media uploads with SHA256 deduplication. YotoShelf uses it as its upstream API layer.

## Installation

```sh
go get gitlab.com/yotoshelf/go-yoto
```

## Quick start

```go
import "gitlab.com/yotoshelf/go-yoto"

client := yoto.NewClient("YOUR_CLIENT_ID", nil, nil)
```

## Authentication

The Yoto API uses Authorization Code + PKCE. Two flows are supported:

### Browser OAuth2 PKCE (web applications)

```go
verifier, err := yoto.GenerateCodeVerifier()
authURL := client.BuildAuthorizeURL(redirectURI, "random-state", verifier)
// redirect user to authURL, receive code in callback
tokens, err := client.ExchangeCode(ctx, code, redirectURI, verifier)
```

### Device code flow (CLI / headless)

```go
dcr, err := client.RequestDeviceCode(ctx)
fmt.Printf("Visit %s and enter code: %s\n", dcr.VerificationURI, dcr.UserCode)
tokens, err := client.PollForToken(ctx, dcr)
```

### Token persistence

Implement the `TokenStore` interface to persist tokens across restarts. A `MemoryTokenStore` is included for testing. Tokens are automatically refreshed 30 seconds before expiry.

## API coverage

**26 of 27 upstream endpoints (96%)** are implemented. Coverage is verified weekly via live smoke tests.

### Devices

| Upstream endpoint | Go method | Status |
|---|---|---|
| `GET /device-v2/devices/mine` | `ListDevices` | Working |
| `GET /device-v2/{id}/config` | `GetDeviceInfo` | Working |
| `PUT /device-v2/{id}/config` | `UpdateDeviceSettings` | Working |
| `POST /device-v2/{id}/command/status` | `SendCommand` | Working |
| `GET /device-v2/{id}/status` | `GetDeviceStatus` | Deprecated — returns 403 |

### Content

| Upstream endpoint | Go method | Status |
|---|---|---|
| `GET /content/mine` | `ListMyCards` | Working |
| `GET /content/{id}` | `GetCard` | Working |
| `POST /content` | `CreateCard` / `UpdateCard` | Working |
| `DELETE /content/{id}` | `DeleteCard` | Working |

## Known API behaviours

- **Device status in config:** `GET /device-v2/{id}/status` returns 403. Device status is embedded in the config response. Use `GetDeviceInfo` instead.
- **Single-use refresh tokens:** Yoto refresh tokens are single-use. The new token must be persisted before the old one is discarded.
- **Account email via family endpoint:** `/userinfo` requires the `openid` scope (not in default scope set). Use `GetUserFamily()` to get the account email.

## Error handling

```go
if errors.Is(err, yoto.ErrNotFound)    {}  // 404
if errors.Is(err, yoto.ErrUnauthorized) {}  // 401
if errors.Is(err, yoto.ErrRateLimited)  {}  // 429

var apiErr *yoto.APIError
if errors.As(err, &apiErr) {
    fmt.Printf("API error %d: %s\n", apiErr.StatusCode, apiErr.Message)
}
```

## Live validation

A weekly scheduled CI pipeline runs the full integration test suite against the real Yoto API to detect upstream drift. If a smoke run detects shape drift, the pipeline fails and the library is updated before the next YotoShelf release.
