docs: expand JavaScript SDK documentation

This commit is contained in:
Mo Elzubeir
2026-05-29 13:35:09 -05:00
parent 252ea713b1
commit c860cf6d88
11 changed files with 850 additions and 116 deletions
+366
View File
@@ -0,0 +1,366 @@
# API Reference
Public API reference for `@socialhose/api`, the TypeScript SDK for the Socialhose Public API.
The SDK is intended for server-side JavaScript/TypeScript. It authenticates requests with an API key, wraps Socialhose REST endpoints, normalizes common response shapes, retries transient failures, applies request timeouts, and caches GET responses.
## Package exports
```ts
import {
SocialhoseClient,
createSocialhoseClient,
SocialhoseError,
MemoryCache,
NoopCache,
type Cache,
type SocialhoseClientOptions,
type RequestOptions,
type AnalyticsFilters,
type MentionFilters,
} from '@socialhose/api';
```
## Authentication
Every request sends:
- `Authorization: Api-Key <apiKey>`
- `Accept: application/json`
- a browser-like `User-Agent` by default
- `Content-Type: application/json` for `POST`
Do not use this SDK directly in browser code. Keep `SOCIALHOSE_API_KEY` on the server.
## `SocialhoseClientOptions`
Configuration object for `new SocialhoseClient(options)` and `createSocialhoseClient(options)`.
- `apiKey: string` — Socialhose API key. Required; empty strings fail on first request.
- `baseUrl?: string` — API root. Default: `https://socialhose.net/api/public/v1`.
- `userAgent?: string` — request user-agent. Default is Chrome-like because the API edge may reject generic Node clients.
- `fetch?: typeof fetch` — custom fetch implementation for tests, instrumentation, or runtimes without global fetch.
- `timeoutMs?: number` — per-attempt timeout. Default: `8000`.
- `retries?: number` — retry count after the first attempt. Default: `3`.
- `retryDelayMs?: (attempt: number) => number` — retry backoff function. Default: exponential backoff with jitter.
- `cacheTtlMs?: number` — default GET cache TTL in milliseconds. Default: `60000`; set `0` to disable the built-in memory cache.
- `cache?: Cache` — custom cache implementation. If supplied, it replaces the internal cache.
- `defaultHeaders?: Record<string, string>` — extra headers sent with every request.
## `RequestOptions`
Per-request options.
- `revalidateSeconds?: number` — override cache TTL for this call, in seconds. Passed only to `Cache.set`; not sent to the API.
- `signal?: AbortSignal` — caller abort signal combined with the SDK timeout.
- `headers?: Record<string, string>` — request-specific headers.
## `new SocialhoseClient(options)`
Creates an authenticated API client.
```ts
const socialhose = new SocialhoseClient({
apiKey: process.env.SOCIALHOSE_API_KEY!,
timeoutMs: 8_000,
retries: 3,
cacheTtlMs: 60_000,
});
```
## `createSocialhoseClient(options)`
Factory wrapper around the constructor.
```ts
const socialhose = createSocialhoseClient({ apiKey });
```
Returns `SocialhoseClient`.
## Low-level HTTP methods
### `client.get<T>(path, params?, options?)`
Performs an authenticated cached GET request.
- `path: string` — endpoint path, with or without leading slash.
- `params?: QueryParams` — query parameters. `undefined`, `null`, and `''` values are omitted.
- `options?: RequestOptions` — cache TTL, abort signal, and extra headers.
Returns `Promise<T>`.
Behavior:
- Builds the URL from `baseUrl`, `path`, and `params`.
- Uses the full URL as the cache key.
- Retries network errors, timeouts, `429`, and `5xx` responses.
- Throws `SocialhoseError` for non-OK responses after retries.
### `client.post<T>(path, body, options?)`
Performs an authenticated JSON POST request.
- `path: string` — endpoint path.
- `body: unknown` — JSON-serialized request body.
- `options?: RequestOptions` — abort signal and extra headers.
Returns `Promise<{ status: number; data: T | null }>`.
Behavior:
- Never uses the GET cache.
- Sends `Content-Type: application/json`.
- Returns the HTTP status so higher-level methods can normalize status-specific outcomes.
- Lets POST `409` responses through for conflict normalization.
## Campaign methods
### `client.getCampaigns(options?)`
Fetches first-page campaigns.
Endpoint: `GET /campaigns/?page=1`
Returns `Promise<Campaign[]>`.
Use it to list accessible campaigns or populate campaign selectors. The helper reads page 1 only; use `get<Paginated<Campaign>>()` for manual pagination.
### `client.getCampaign(id, options?)`
Fetches one campaign by ID.
Endpoint: `GET /campaigns/{id}/`
Returns `Promise<Campaign>`.
Throws `SocialhoseError` when the campaign is missing or inaccessible.
### `client.getCampaignIdByMatch(match, options?)`
Returns the first campaign ID whose name contains `match`, case-insensitively.
Returns `Promise<string | undefined>`.
If campaign fetching fails or no campaign matches, returns `undefined`. Use exact IDs when names may collide.
## Analytics filters
`AnalyticsFilters` are passed through as query params:
- `campaign_ids?: string` — one ID or comma-separated IDs.
- `date_from?: string` — lower date bound, commonly `YYYY-MM-DD`.
- `date_to?: string` — upper date bound, commonly `YYYY-MM-DD`.
- `platforms?: string` — platform or comma-separated platforms.
- `sentiments?: string` — sentiment or comma-separated sentiments.
## Analytics methods
### `client.getOverview(filters?, options?)`
Endpoint: `GET /analytics/overview/`
Returns `Promise<Overview>` with total mentions, authors, estimated reach, sentiment distribution, platform breakdown, engagement, and growth metrics.
The SDK coerces `total_mentions` and `total_authors` to numbers because some API responses may return numeric strings.
### `client.getTimeline(filters?, options?)`
Endpoint: `GET /analytics/timeline/`
Filters include `interval?: 'day' | 'week' | 'month'`, defaulting to `day`.
Returns `Promise<TimelinePoint[]>`. If the API response lacks `series`, returns `[]`.
### `client.getSentiment(filters?, options?)`
Endpoint: `GET /analytics/sentiment/`
Returns `Promise<{ distribution: SentimentSplit; by_platform: Record<string, SentimentSplit> }>`.
### `client.getShareOfVoice(filters?, options?)`
Endpoint: `GET /analytics/share-of-voice/`
Returns `Promise<{ total_mentions: number; campaigns: ShareOfVoiceItem[] }>`.
Each campaign row includes mention count, share percentage, engagement, and sentiment.
### `client.getPlatforms(filters?, options?)`
Endpoint: `GET /analytics/platforms/`
Returns `Promise<PlatformStat[]>`. If the API response lacks `platforms`, returns `[]`.
### `client.getTopKeywords(filters?, options?)`
Endpoint: `GET /analytics/top-keywords/`
Filters include `limit?: number`, defaulting to `12`.
Returns `Promise<KeywordStat[]>`. If the API response lacks `keywords`, returns `[]`.
### `client.getTrending(filters?, options?)`
Endpoint: `GET /analytics/trending/`
Filters include `limit?: number`, defaulting to `8`.
Returns `Promise<TrendingItem[]>`. If the API response lacks `trending`, returns `[]`.
### `client.getTopMentions(filters?, options?)`
Endpoint: `GET /analytics/top-mentions/`
Filters include `limit?: number`, defaulting to `6`.
Returns `Promise<TopMention[]>`. If the API response lacks `mentions`, returns `[]`.
## Mention methods
### `client.getMentions(filters?, optionsOrRevalidate?)`
Endpoint: `GET /mentions/`
Returns `Promise<Paginated<Mention>>`.
`MentionFilters` include all `AnalyticsFilters` plus:
- `page?: number` — page number, defaulting to `1`.
- `content_search?: string` — text search term.
- `ordering?: string` — sort key, such as `-published_at` or `-engagement_count`.
The second argument may be `RequestOptions` or the legacy shorthand `number` for `revalidateSeconds`.
## Mailing-list methods
### `client.getMailingLists(options?)`
Endpoint: `GET /mailing-lists/?page=1`
Returns `Promise<MailingList[]>` from the first page.
### `client.inviteMailingListMember(listId, invite, options?)`
Endpoint: `POST /mailing-lists/{listId}/members/`
`invite` fields:
- `email: string`
- `first_name?: string`
- `last_name?: string`
- `invitation_message?: string`
Returns `Promise<InviteResult>`:
- `{ outcome: 'invited', invitation }` for `201` plus `status: 'invited'`.
- `{ outcome: 'already', detail }` for `409` conflicts.
- `{ outcome: 'error', detail }` for unexpected successful/conflict response shapes.
Other non-OK statuses throw `SocialhoseError`.
## Entity analytics methods
Entity analytics are SDK-composed views built on `/mentions/` with `content_search`. They provide term/person/org/topic analytics when native analytics endpoints are campaign-scoped.
### `client.getEntityBrief(term, campaignId?, options?)`
Runs one mention search ordered by engagement.
Returns `Promise<EntityBrief>`:
- `term` — requested search term.
- `total` — exact API count.
- `exact` — true when the sample covers the full population.
- `sentiment` — sample-derived sentiment split.
- `platformMix` — sample-derived platform counts.
- `sample` — first page ordered by engagement.
`total` is exact. Sentiment and platform mix are exact only when `exact` is true.
### `client.getEntityStats(term, campaignId?, options?)`
Builds a richer dashboard for one term.
Returns `Promise<EntityStats>` with `EntityBrief` fields plus:
- `recent` — newest mentions.
- `recent7d` — latest seven-day count from the sparkline.
- `prev7d` — previous seven-day count.
- `momentumPct` — seven-day percentage change, or `null` if not computable.
- `sparkline` — daily counts for the last 14 days when date faceting is trustworthy.
Failure model:
- The initial brief must succeed.
- Follow-up subrequests are best-effort.
- Exact facets are used only when reconciliation checks pass; otherwise sample-derived values are retained.
### `client.getEntityBriefs(terms, campaignId?, concurrency?, options?)`
Fetches many entity briefs with bounded concurrency.
Returns `Promise<Map<string, EntityBrief>>`.
Failed terms are omitted from the map. Default concurrency is `20`; reduce it under rate limits.
## Cache API
### `Cache`
```ts
interface Cache {
get(key: string): Promise<unknown | undefined>;
set(key: string, value: unknown, ttlMs: number): Promise<void>;
delete(key: string): Promise<void>;
}
```
The key is the full GET URL. `get()` returns `undefined` on cache miss. `ttlMs` is in milliseconds.
### `MemoryCache`
In-memory `Map` cache with per-entry TTL. Good for tests, scripts, and single-process services. Not enough for distributed rate-limit protection.
### `NoopCache`
Cache implementation that never stores values. Use it to force fresh data or disable caching explicitly.
### `client.clearCache()`
Deletes cache keys used by the current client instance. It is not a global purge for shared cache backends.
## `SocialhoseError`
Structured request failure.
Fields:
- `name: 'SocialhoseError'`
- `status?: number`
- `path: string`
- `body?: string`
- `cause?: unknown`
Thrown after retry exhaustion or unsupported non-OK HTTP responses.
## Core data types
- `Sentiment``'positive' | 'negative' | 'neutral'`.
- `SentimentSplit``{ positive: number; negative: number; neutral: number }`.
- `Paginated<T>``{ count, next, previous, results }`.
- `Campaign` — campaign metadata.
- `Overview` — aggregate analytics.
- `TimelinePoint` — one time-series bucket.
- `ShareOfVoiceItem` — campaign comparison row.
- `PlatformStat` — platform count and engagement.
- `KeywordStat` — keyword count and sentiment.
- `TrendingItem` — keyword momentum.
- `TopMention` — compact high-impact mention.
- `Mention` — full mention record with content, engagement, metadata, and author profile.
- `MailingList` — mailing-list metadata.
- `MailingListInvitation` — mailing-list invitation state.
- `InviteResult` — normalized invite result.
- `PlatformShare` — entity analytics platform count.
- `EntityBrief` — compact term analytics.
- `EntityStats` — full term dashboard.
+104
View File
@@ -0,0 +1,104 @@
# 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<unknown | undefined>;
set(key: string, value: unknown, ttlMs: number): Promise<void>;
delete(key: string): Promise<void>;
}
```
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<string | null>;
set(k: string, v: string, mode: 'PX', ttl: number): Promise<unknown>;
del(k: string): Promise<unknown>;
}) {}
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.
+85
View File
@@ -0,0 +1,85 @@
# Entity Analytics
Entity analytics are SDK-composed analytics for a single term, person, organization, hashtag, product, or incident.
They exist because Socialhose analytics endpoints are campaign-scoped, while `/mentions/` supports `content_search` and returns an exact `count`.
## `getEntityBrief()`
`getEntityBrief(term, campaignId?)` performs one mention search ordered by engagement.
It returns:
- exact total mention count
- top mention sample
- sample-derived sentiment split
- sample-derived platform mix
- `exact` flag indicating whether the sample covers the full population
This is cheap enough for grids, tables, and autocomplete-like entity lists.
## `getEntityStats()`
`getEntityStats(term, campaignId?)` expands a brief into a detail dashboard.
It fetches:
- engagement-ordered sample
- newest mentions
- sentiment facets
- platform facets
- cumulative date boundaries for a 14-day sparkline
The first brief request is required. Subrequests are best-effort; failures return fallbacks instead of zeroing out the entity.
## Accuracy safeguards
### Sentiment reconciliation
The SDK facets `/mentions/` by sentiment and accepts exact sentiment only when:
```txt
positive + negative + neutral === total
```
If the sum does not match, the SDK assumes a filter was ignored or misapplied and falls back to sample-derived sentiment.
### Platform reconciliation
The SDK facets `/mentions/` over known platform keys. If the platform facet sum exceeds the entity total, the SDK assumes the API ignored a filter and falls back to sample-derived platform mix.
A sum below total is allowed because some mentions may be on platforms outside the SDK's known list.
### Timeline cumulative differencing
The SDK avoids adjacent `[date_from, date_to]` daily windows because inclusive `date_to` can double-count boundary days.
Instead it requests cumulative counts with `date_from` only:
```txt
count(on/after day N) - count(on/after day N+1) = day N count
```
If the earliest cumulative count exceeds the known entity total, the SDK treats date-filtered `content_search` as untrustworthy and returns an empty sparkline rather than wrong bars.
## Rate-limit guidance
A single `getEntityStats()` can perform approximately:
- 1 brief request
- 1 recent request
- 3 sentiment facet requests
- 6 platform facet requests
- 15 timeline boundary requests
Use caching and keep batch concurrency low.
Recommended pattern:
```ts
const stats = await socialhose.getEntityStats('cholera', campaignId, {
revalidateSeconds: 900,
});
```
Use `getEntityBriefs()` for lists and call `getEntityStats()` only for selected detail views.
+173
View File
@@ -0,0 +1,173 @@
# Usage Guide
## What this SDK is
`@socialhose/api` is a TypeScript client for the Socialhose Public API. It is built for backend services, scripts, serverless functions, and dashboards that need campaign analytics, mention search, mailing-list management, and term-level entity analytics.
The SDK handles:
1. API-key authentication.
2. Typed endpoint helpers.
3. Timeouts, retries, and request cancellation.
4. GET caching with an injectable cache interface.
5. Entity analytics assembled from `/mentions/` when native analytics endpoints are campaign-scoped.
## Install
```bash
npm install @socialhose/api
```
Requires Node 18+ or a custom `fetch` implementation.
## Basic setup
```ts
import { SocialhoseClient } from '@socialhose/api';
const socialhose = new SocialhoseClient({
apiKey: process.env.SOCIALHOSE_API_KEY!,
});
```
Keep the API key server-side. Do not instantiate this client in browser code.
## Production setup
```ts
const socialhose = new SocialhoseClient({
apiKey: process.env.SOCIALHOSE_API_KEY!,
timeoutMs: 10_000,
retries: 3,
cacheTtlMs: 60_000,
});
```
Recommendations:
- Keep the default browser-like user-agent unless you have verified an alternative works.
- Use a shared cache such as Redis for multi-process or serverless deployments.
- Reduce entity batch concurrency if you see `429` responses.
- Use `revalidateSeconds` for dashboards so repeated views do not fan out fresh requests.
## List campaigns
```ts
const campaigns = await socialhose.getCampaigns();
for (const campaign of campaigns) {
console.log(campaign.id, campaign.name, campaign.status);
}
```
## Search mentions
```ts
const page = await socialhose.getMentions({
campaign_ids: 'campaign-id',
content_search: 'hospital',
platforms: 'twitter,reddit',
sentiments: 'negative',
ordering: '-published_at',
});
console.log(page.count);
console.log(page.results[0]?.content);
```
## Fetch campaign dashboard analytics
```ts
const filters = { campaign_ids: 'campaign-id', date_from: '2026-05-01' };
const [overview, timeline, sentiment, platforms, topMentions] = await Promise.all([
socialhose.getOverview(filters),
socialhose.getTimeline({ ...filters, interval: 'day' }),
socialhose.getSentiment(filters),
socialhose.getPlatforms(filters),
socialhose.getTopMentions({ ...filters, limit: 10 }),
]);
```
## Analyze a term or entity
```ts
const stats = await socialhose.getEntityStats('RSF', 'campaign-id', {
revalidateSeconds: 900,
});
console.log({
total: stats.total,
sentiment: stats.sentiment,
platformMix: stats.platformMix,
momentumPct: stats.momentumPct,
topUrls: stats.sample.slice(0, 3).map((m) => m.url),
});
```
Use `getEntityBrief()` for cheap cards/lists and `getEntityStats()` for detail pages.
## Batch entity briefs
```ts
const briefs = await socialhose.getEntityBriefs(
['Burhan', 'Hemedti', 'SAF', 'RSF'],
'campaign-id',
5,
{ revalidateSeconds: 3600 },
);
```
Failed terms are skipped. Lower concurrency if rate limits are visible.
## Pagination
`getCampaigns()` and `getMailingLists()` return first-page arrays. `getMentions()` exposes a `page` filter.
For manual pagination, use `get()`:
```ts
let page = 1;
while (true) {
const response = await socialhose.get('/mentions/', { page, content_search: 'cholera' });
// process response.results
if (!response.next) break;
page += 1;
}
```
## Error handling
```ts
import { SocialhoseError } from '@socialhose/api';
try {
await socialhose.getOverview({ campaign_ids: 'bad-id' });
} catch (error) {
if (error instanceof SocialhoseError) {
console.error({ status: error.status, path: error.path, body: error.body });
} else {
throw error;
}
}
```
## Caching
GET requests are cached; POST requests are not.
```ts
await socialhose.getMentions(
{ content_search: 'sudan' },
{ revalidateSeconds: 900 },
);
```
The default cache is process-local. Use a persistent/shared cache for production dashboards and rate-limit control.
## Operational pitfalls
- Entity analytics are request-heavy. One `getEntityStats()` call can issue roughly 20+ requests.
- The API rate limit is roughly 60 requests/minute per API key.
- The entity timeline uses cumulative differencing intentionally to avoid inclusive `date_to` double-counting.
- Exact entity sentiment/platform values are used only when facet counts reconcile with the known total.
- `409` mailing-list conflicts are normalized to `{ outcome: 'already' }`; other non-OK responses throw.