# Blizzard API Compliance

All modules must comply with [Blizzard's API Terms of Use](https://develop.battle.net/documentation). These are legally binding requirements, not suggestions.

## 30-day data TTL

All Blizzard-sourced data must be refreshed or deleted within 30 days. Every synced record carries a freshness timestamp:

- **Content entities** use a `last_fetched` base field. This includes catalogs (`wow_character`, `wow_guild`, `wow_achievement`, `wow_item`, `wow_mount`, `wow_pet`, `wow_title`, `wow_toy`) and per-owner junction rows (`wow_achievement_progress`, `wow_collected_pet`, `wow_mount_collection`, `wow_reputation_standing`, `wow_title_award`, `wow_toy_collection`).
- **Taxonomy terms** synced from reference endpoints (`wow_realm`, `wow_playable_class`, `wow_playable_race`, `wow_playable_specialization`, `wow_power_type`, `wow_reputation_faction`) use a `wow_last_fetched` configurable field. Drupal taxonomy terms can't host arbitrary base fields, so the wow submodules ship the field as config rather than declaring it on a custom entity.

The `wow.ttl_sweeper` service deletes content entities whose `last_fetched` exceeds the 30-day window. It runs from `hook_cron`, throttled to at most once per 23 hours (state key `wow.ttl_sweep_last`), or on demand via `drush wow:sweep-ttl`. Deletion happens in chunks of 50 entities to keep memory bounded. The `SWEEP_ENTITY_TYPES` constant on `TtlSweeper` enumerates every entity type swept — both catalog entities (`wow_achievement`, `wow_character`, `wow_guild`, `wow_item`, `wow_mount`, `wow_pet`, `wow_title`, `wow_toy`) and per-owner junction rows (`wow_achievement_progress`, `wow_collected_pet`, `wow_mount_collection`, `wow_reputation_standing`, `wow_title_award`, `wow_toy_collection`). The `TtlSweepCoverageTest` regression test scans every entity class for `last_fetched` and asserts each is registered in the sweep set, so adding a new catalog entity without wiring the sweep fails CI.

Junction entities need their own TTL because Blizzard's character profile API can return a partial collection (large accounts) and because un-collected items disappear from the API but stay in local storage until swept.

Taxonomy reference data is **not** swept — operators keep it fresh via the scheduled sync commands (`drush wow:sync-realms` etc.). Pair them with a cron schedule to stay inside the window.

Blizzard's Terms allow validating that a character ID still exists as a lighter-weight alternative to a full profile re-fetch. The suite does not currently use that path: `CharacterSync::refresh()` returns `NULL` on 403 / 404 without deleting the entity, and the TTL sweeper is the single deletion mechanism for stale characters. A validate-and-delete path is a reasonable future optimization, but it is not a current requirement — the 30-day sweep already satisfies the ToS contract.

## Rate limiting

The API allows 36,000 calls per hour and 100 per second (Blizzard's documented hard caps). `BattleNetClient` enforces both ceilings — the per-hour and per-second counters are stored in Drupal State (`wow.api_rate_limit`, `wow.api_rate_limit_second`) so every PHP process (web, drush, queue workers) shares one view, and check + increment is serialized under the `wow:api_rate_limit` lock to prevent concurrent overshoot.

When either ceiling is full, `get()` throws a `RuntimeException` with a "rate limit reached" message — it does not sleep, retry silently, or return `NULL`. If the `wow:api_rate_limit` lock itself cannot be acquired after a few retries, the client refuses to mutate the counters and throttles the request rather than risk a concurrent overshoot — see [lifecycle → Rate limiting](../architecture/lifecycle.md#rate-limiting) for the mechanics. Queue-based syncs (achievements, mounts, pets, titles, toys) naturally pace themselves through the Advanced Queue processor, so rate-limit exceptions generally only surface for ad-hoc drush calls or misconfigured external callers.

Monthly call totals are tracked separately in State (`wow.api_calls.YYYY_MM`) as a diagnostic counter. They do not gate requests — only the hourly and per-second windows do.

## Media caching

Blizzard strongly recommends caching icon and media assets locally rather than hotlinking their CDN. The `cache_icons` setting in the core module controls this behavior. When enabled, icons are downloaded to `public://wow/<type>/` on first fetch and served from local storage.

## User data deletion

If a user de-links their Battle.net account or requests account deletion, all their Blizzard-sourced data must be immediately purged. Two cascade paths are wired:

- **Account deletion** — `wow_user_entity_predelete` runs on the user entity and calls `wow_user_purge_account_data($uid)`, which deletes the user's `wow_character` rows and clears OAuth tokens / selection state from `user.data`.
- **Battle.net de-link** — `wow_user_form_openid_connect_accounts_form_alter` adds a submit handler that calls the same purge function when the user clicks "Disconnect" for the `battlenet` client on `/user/{uid}/connected-accounts`. The alter is scoped to the `battlenet` client only so non-Blizzard OIDC disconnections don't trigger the purge.

Character deletion in turn fires `wow_character_entity_predelete`, which dispatches `deleteCharacterData()` to every registered `CharacterDataProvider` plugin (achievements, mounts, pets, titles, toys, reputation), purging all related collection data. One provider throwing does not block the rest — each is caught and logged. The same pattern applies to guilds via `wow_guild_entity_predelete` and `GuildDataProvider` (the interface ships today; no in-tree plugin implementations yet).

## API key security

The client secret must never appear in configuration exports, logs, or error messages. The module enforces this by storing the secret via the Key module, which supports environment variables and file-based providers that keep secrets out of the database.

## Attribution

You must credit Blizzard as the data source on any page displaying API-sourced data. You may not use Blizzard trademarks in your application's name or URL in a way that implies endorsement.

## No monetization of API data

You may not charge for features that use Blizzard API data, place API-sourced content behind paywalls, serve forced ads alongside it, or sell the data to third parties.
