YotoShelf
Reference

go-yoto Client

go-yoto is a zero-dependency Go client for the Yoto API. Used internally by YotoShelf and available for other Go projects.

Source gitlab.com/yotoshelf/go-yoto Version v0.3.1 Verified

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.

Version status

VersionStatusNotes
v0.3.1 Current In use by YotoShelf main branch
v0.3.2 Planned Minor additions; no breaking changes expected
v1.0 Horizon API stabilisation; possible Channels type change; GetDeviceStatus removal

Installation

go get gitlab.com/yotoshelf/go-yoto

Quick start

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)

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)

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. Refresh token rotation is mutex-protected and safe for concurrent use.

client := yoto.NewClient("YOUR_CLIENT_ID", &yoto.MemoryTokenStore{}, nil)

API coverage

26 of 27 upstream endpoints (96%) are implemented. 149 of 149 response fields (100%) are validated against the live API via the yoto-explorer harness. Coverage is verified on each CI smoke run via response shape assertions.

Devices

Upstream endpointGo methodStatus
GET /device-v2/devices/mineListDevicesWorking
GET /device-v2/{id}/configGetDeviceInfoWorking
GET /device-v2/{id}/configGetDeviceSettingsWorking (settings only)
PUT /device-v2/{id}/configUpdateDeviceSettingsWorking
POST /device-v2/{id}/command/statusSendCommandWorking
PUT /device-v2/{id}/shortcutsUpdateShortcutConfigWorking (beta)
GET /device-v2/{id}/statusGetDeviceStatusDeprecated — returns 403

Content

Upstream endpointGo methodStatus
GET /content/mineListMyCardsWorking
GET /content/mine?showdeletedListMyCardsWithOptionsWorking
GET /content/{id}GetCardWorking
GET /content/{id}?playable&signingTypeGetCardPlayableWorking
GET /content/{id}?timezoneGetCardWithOptionsWorking
POST /contentCreateCard / UpdateCardWorking
DELETE /content/{id}DeleteCardWorking

Family & library

Upstream endpointGo methodStatus
GET /user/familyGetUserFamilyWorking
GET /card/family/library/groupsListLibraryGroupsWorking
GET /card/family/library/groups/{id}GetLibraryGroupWorking
POST /card/family/library/groupsCreateLibraryGroupWorking
PUT /card/family/library/groups/{id}UpdateLibraryGroupWorking
DELETE /card/family/library/groups/{id}DeleteLibraryGroupWorking

Media

Upstream endpointGo methodStatus
GET /media/transcode/audio/uploadUrlUploadAudioWorking
POST /media/displayIcons/user/me/uploadUploadIconWorking
POST /media/coverImage/user/me/uploadUploadCoverImageWorking
GET /media/displayIcons/user/yotoListPublicIconsWorking
GET /media/displayIcons/user/meListMyIconsWorking
GET /media/family/imagesListFamilyImagesWorking
GET /media/family/images/{id}GetFamilyImageURLWorking
POST /media/family/imagesUploadFamilyImageWorking

Known API behaviours

MYO vs streaming cards

All cards appear in /content/mine. Distinguish them by:

  • MYO / purchased: config.OnlineOnly is false (or absent), no editSettings.RssUrl. Track format is "aac" with type "audio".
  • Streaming / podcast: config.OnlineOnly is true, has editSettings.RssUrl. Track type is "stream".

Device status embedded in config

GET /device-v2/{id}/status returns HTTP 403. Device status (active card, battery level, charging state, etc.) is embedded in the config response. Use GetDeviceInfo to access both settings and status.

Account email via family endpoint

/userinfo requires the openid scope (not in the default scope set). To get the account email, call GetUserFamily() — the primary address is at family.Members[0].Email.

Signed audio URLs

To get playable audio URLs, fetch a card with GetCardPlayable. Track URLs are signed S3 URLs and are time-limited.

Live validation

A weekly scheduled CI pipeline runs the full integration test suite against the real Yoto API to detect upstream drift. The pipeline has two stages:

  1. refresh-token — exchanges the stored refresh token for a fresh access token and persists the rotated refresh token back to the CI variable. Yoto refresh tokens are single-use.
  2. smoke-test — runs integration tests with the fresh access token, including response shape assertions against every tracked field.

If a smoke run detects shape drift (new or removed fields), the pipeline fails and the library is updated before the next YotoShelf release. To rebaseline the field manifest after an intentional upstream change, update the explore/ fixtures and re-run with REBASELINE=1.

Error handling

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)
}