# Caching, Retries, and Failure Semantics ## GET lifecycle 1. Build full URL from `baseUrl`, `path`, and query params. 2. Check cache by full URL. 3. Fetch with auth headers and timeout on cache miss. 4. Retry network errors, timeout errors, `429`, and `5xx`. 5. Parse JSON. 6. Throw `SocialhoseError` for unsupported non-OK responses. 7. Store successful parsed response in cache. ## POST lifecycle 1. Build URL. 2. Fetch JSON body with auth headers and timeout. 3. Retry network errors, timeout errors, `429`, and `5xx`. 4. Parse JSON. 5. Return `{ status, data }`. 6. Never cache. ## Retry policy Defaults: ```ts retries: 3, retryDelayMs: (attempt) => 400 * 2 ** attempt + Math.random() * 200, timeoutMs: 8_000, ``` Retries apply to transient conditions only: - network failures - SDK timeout/abort failures - `429 Too Many Requests` - `5xx` server errors Retries do not apply to normal client errors such as `400`, `401`, `403`, and `404`. ## Cache contract ```ts interface Cache { get(key: string): Promise; set(key: string, value: unknown, ttlMs: number): Promise; delete(key: string): Promise; } ``` The cache key is the full request URL. `ttlMs` is in milliseconds and comes from `cacheTtlMs` or per-request `revalidateSeconds`. ## Redis-style cache example ```ts import { SocialhoseClient, type Cache } from '@socialhose/api'; class RedisJsonCache implements Cache { constructor(private redis: { get(k: string): Promise; set(k: string, v: string, mode: 'PX', ttl: number): Promise; del(k: string): Promise; }) {} async get(key: string) { const value = await this.redis.get(key); return value == null ? undefined : JSON.parse(value); } async set(key: string, value: unknown, ttlMs: number) { if (ttlMs <= 0) return; await this.redis.set(key, JSON.stringify(value), 'PX', ttlMs); } async delete(key: string) { await this.redis.del(key); } } const socialhose = new SocialhoseClient({ apiKey: process.env.SOCIALHOSE_API_KEY!, cache: new RedisJsonCache(redis), }); ``` ## Method failure semantics Campaign, analytics, mention, and list methods: - Throw `SocialhoseError` after retry exhaustion or unsupported non-OK responses. - Return normalized arrays when the API wraps arrays in properties such as `series`, `platforms`, or `keywords`. `inviteMailingListMember()`: - `201 + status: invited` -> `outcome: 'invited'`. - `409` -> `outcome: 'already'`. - Unexpected success/conflict shapes -> `outcome: 'error'`. - Other non-OK statuses throw. Entity methods: - `getEntityBrief()` throws if the mention search fails. - `getEntityStats()` requires the initial brief but treats later subrequests as best-effort. - `getEntityBriefs()` skips failed terms and returns successful entries.