tredi-sdk 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +55 -0
- package/LICENSE +21 -0
- package/README.md +278 -0
- package/dist/index.cjs +912 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +798 -0
- package/dist/index.d.ts +798 -0
- package/dist/index.js +890 -0
- package/dist/index.js.map +1 -0
- package/docs/README.md +14 -0
- package/docs/api-reference.md +452 -0
- package/docs/architecture.md +172 -0
- package/docs/guides.md +322 -0
- package/examples/fetch-insights.ts +32 -0
- package/examples/oauth-flow.ts +55 -0
- package/examples/publish-text.ts +39 -0
- package/examples/reply-autopilot.ts +62 -0
- package/package.json +76 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/constants.ts","../src/errors.ts","../src/logger.ts","../src/http.ts","../src/resources/base.ts","../src/resources/insights.ts","../src/resources/mentions.ts","../src/resources/posts.ts","../src/resources/profile.ts","../src/resources/publishing.ts","../src/resources/replies.ts","../src/resources/search.ts","../src/client.ts","../src/oauth.ts"],"names":["sleep"],"mappings":"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","file":"index.js","sourcesContent":["/**\n * Static configuration for the Threads API.\n *\n * Values here mirror the official documentation\n * (https://developers.facebook.com/docs/threads). Hosts and the API version\n * are the only \"magic strings\" in the SDK and live in one place so they are\n * easy to audit against the docs.\n */\n\n/** Graph host for all data/publishing calls. */\nexport const DEFAULT_BASE_URL = 'https://graph.threads.net'\n\n/** Host that serves the OAuth authorization window (user-facing redirect). */\nexport const AUTHORIZATION_BASE_URL = 'https://threads.net'\n\n/** Current Threads Graph API version. */\nexport const DEFAULT_API_VERSION = 'v1.0'\n\n/** Default per-request timeout. */\nexport const DEFAULT_TIMEOUT_MS = 30_000\n\n/** Default automatic-retry policy applied to idempotent requests. */\nexport const DEFAULT_RETRY = {\n maxRetries: 2,\n initialDelayMs: 500,\n maxDelayMs: 8_000,\n backoffFactor: 2,\n} as const\n\n/**\n * Graph error codes that indicate throttling. Used only as a secondary signal;\n * the primary rate-limit signal is HTTP 429. Kept intentionally small — only\n * widely-documented throttle codes are listed.\n */\nexport const RATE_LIMIT_ERROR_CODES: ReadonlySet<number> = new Set([4, 17, 32, 613])\n\n/** Graph error code for an invalid/expired access token. */\nexport const INVALID_TOKEN_ERROR_CODE = 190\n\n/**\n * OAuth permission scopes supported by the Threads API, as documented.\n * `threads_basic` is required for every call.\n */\nexport const THREADS_SCOPES = [\n 'threads_basic',\n 'threads_content_publish',\n 'threads_read_replies',\n 'threads_manage_replies',\n 'threads_manage_insights',\n 'threads_keyword_search',\n 'threads_delete',\n 'threads_location_tagging',\n] as const\n\n/**\n * A documented Threads scope, or any other scope string the API may accept.\n * The string fallback keeps autocomplete for known scopes without hardcoding\n * scope names that aren't verified against the docs.\n */\nexport type ThreadsScope = (typeof THREADS_SCOPES)[number] | (string & {})\n","/**\n * Typed error hierarchy. Every failure the SDK surfaces is an instance of\n * {@link ThreadsError}, so callers can `catch` broadly or narrow with\n * `instanceof` to a specific subtype.\n *\n * ```text\n * ThreadsError\n * ├─ ThreadsValidationError client-side input was invalid (no request sent)\n * ├─ ThreadsTimeoutError request exceeded the configured timeout\n * ├─ ThreadsNetworkError fetch failed before a response was received\n * └─ ThreadsAPIError the API returned a non-2xx response\n * ├─ ThreadsAuthError invalid/expired token or auth failure (401 / code 190)\n * └─ ThreadsRateLimitError throttled (HTTP 429 or a known throttle code)\n * ```\n */\n\nimport { INVALID_TOKEN_ERROR_CODE, RATE_LIMIT_ERROR_CODES } from './constants.js'\n\nexport interface ApiErrorDetails {\n message: string\n /** HTTP status code of the response. */\n status?: number\n /** Graph API `error.code`. */\n code?: number\n /** Graph API `error.error_subcode`. */\n subcode?: number\n /** Graph API `error.type`. */\n type?: string\n /** Graph API `error.fbtrace_id` — quote this when contacting Meta support. */\n fbtraceId?: string\n}\n\n/** Base class for every error thrown by the SDK. */\nexport class ThreadsError extends Error {\n constructor(message: string, options?: { cause?: unknown }) {\n super(message, options)\n this.name = new.target.name\n // Preserve the prototype chain so `instanceof` works across the compiled\n // (downleveled) output, not just in the original TS.\n Object.setPrototypeOf(this, new.target.prototype)\n }\n}\n\n/** Thrown when caller input fails validation before any request is made. */\nexport class ThreadsValidationError extends ThreadsError {}\n\n/** Thrown when a request exceeds the configured timeout. */\nexport class ThreadsTimeoutError extends ThreadsError {}\n\n/** Thrown when the request fails at the network layer (no HTTP response). */\nexport class ThreadsNetworkError extends ThreadsError {}\n\n/** Thrown for any non-2xx API response. */\nexport class ThreadsAPIError extends ThreadsError {\n readonly status?: number\n readonly code?: number\n readonly subcode?: number\n readonly type?: string\n readonly fbtraceId?: string\n\n constructor(details: ApiErrorDetails, options?: { cause?: unknown }) {\n super(details.message, options)\n this.status = details.status\n this.code = details.code\n this.subcode = details.subcode\n this.type = details.type\n this.fbtraceId = details.fbtraceId\n }\n}\n\n/** Invalid or expired access token / authorization failure. */\nexport class ThreadsAuthError extends ThreadsAPIError {}\n\n/** Request was throttled. {@link retryAfterMs} is set when the API tells us. */\nexport class ThreadsRateLimitError extends ThreadsAPIError {\n readonly retryAfterMs?: number\n\n constructor(\n details: ApiErrorDetails & { retryAfterMs?: number },\n options?: { cause?: unknown },\n ) {\n super(details, options)\n this.retryAfterMs = details.retryAfterMs\n }\n}\n\n/** Minimal shape of a Graph API error envelope. */\ninterface GraphErrorBody {\n error?: {\n message?: string\n type?: string\n code?: number\n error_subcode?: number\n fbtrace_id?: string\n }\n}\n\n/** A `Headers`-like object (only `get` is needed). */\ninterface HeadersLike {\n get(name: string): string | null\n}\n\n/**\n * Parses a `Retry-After` header (seconds or HTTP-date) into milliseconds.\n * Returns `undefined` when absent or unparseable.\n */\nexport function parseRetryAfterMs(value: string | null): number | undefined {\n if (!value) return undefined\n const seconds = Number(value)\n if (Number.isFinite(seconds)) return Math.max(0, seconds * 1000)\n const date = Date.parse(value)\n if (Number.isNaN(date)) return undefined\n return Math.max(0, date - Date.now())\n}\n\n/**\n * Maps a non-2xx HTTP response into the most specific error subtype.\n *\n * @param status - HTTP status code.\n * @param body - Parsed JSON body (may be undefined for empty/non-JSON bodies).\n * @param headers - Response headers, used to read `Retry-After`.\n */\nexport function toApiError(\n status: number,\n body: unknown,\n headers?: HeadersLike,\n): ThreadsAPIError {\n const error = (body as GraphErrorBody | undefined)?.error\n const details: ApiErrorDetails = {\n message: error?.message ?? `Threads API request failed with HTTP ${status}`,\n status,\n code: error?.code,\n subcode: error?.error_subcode,\n type: error?.type,\n fbtraceId: error?.fbtrace_id,\n }\n\n if (status === 401 || details.code === INVALID_TOKEN_ERROR_CODE) {\n return new ThreadsAuthError(details)\n }\n\n if (status === 429 || (details.code != null && RATE_LIMIT_ERROR_CODES.has(details.code))) {\n return new ThreadsRateLimitError({\n ...details,\n retryAfterMs: parseRetryAfterMs(headers?.get('retry-after') ?? null),\n })\n }\n\n return new ThreadsAPIError(details)\n}\n","/**\n * Logging hooks. The SDK never writes to the console itself; it calls the\n * injected logger so the host app controls transport and level. The SDK's own\n * log calls (in `http.ts`) only ever include a redacted URL — never raw\n * request params or the access token — via {@link redactUrl}.\n *\n * {@link redactParams} is exported as a standalone utility for host apps that\n * build a custom logger and want to log request params (not just the URL)\n * safely; the SDK itself doesn't need it since it never logs params.\n */\n\nexport type LogLevel = 'debug' | 'info' | 'warn' | 'error'\n\nexport interface LogContext {\n [key: string]: unknown\n}\n\nexport interface Logger {\n /**\n * Receives a structured log event. Implementations must be non-throwing and\n * fast; the SDK calls this synchronously on the request path.\n */\n log(level: LogLevel, message: string, context?: LogContext): void\n}\n\n/** Discards all logs. Default when no logger is configured. */\nexport const noopLogger: Logger = { log() {} }\n\n/** Query/body keys whose values must never be logged. */\nconst SENSITIVE_KEYS: ReadonlySet<string> = new Set([\n 'access_token',\n 'client_secret',\n 'code',\n])\n\nconst REDACTED = 'REDACTED'\n\n/**\n * Returns a copy of `params` with sensitive values masked. Non-sensitive\n * values are preserved so logs stay useful for debugging.\n */\nexport function redactParams(\n params: Record<string, unknown> | undefined,\n): Record<string, unknown> {\n if (!params) return {}\n const out: Record<string, unknown> = {}\n for (const [key, value] of Object.entries(params)) {\n out[key] = SENSITIVE_KEYS.has(key) ? REDACTED : value\n }\n return out\n}\n\n/**\n * Masks sensitive query-string values in a URL. Falsy/relative inputs are\n * returned with a best-effort regex redaction so we never throw on the log\n * path.\n */\nexport function redactUrl(url: string): string {\n try {\n const parsed = new URL(url)\n for (const key of SENSITIVE_KEYS) {\n if (parsed.searchParams.has(key)) parsed.searchParams.set(key, REDACTED)\n }\n return parsed.toString()\n } catch {\n return url.replace(\n /(access_token|client_secret|code)=[^&\\s]+/gi,\n `$1=${REDACTED}`,\n )\n }\n}\n","/**\n * The single HTTP engine used by both the client and the OAuth helpers.\n *\n * Responsibilities: build the request, enforce a timeout, send via the injected\n * `fetch`, parse the response, map failures to typed errors, retry idempotent\n * failures with exponential backoff + jitter, and emit redacted logs.\n *\n * Retry safety: non-idempotent (POST) requests are only retried when the server\n * never processed them (HTTP 429) or no response was received (network error).\n * A POST timeout or 5xx is *not* retried — it may have already taken effect\n * (e.g. a published post), and re-sending could duplicate it.\n */\n\nimport { DEFAULT_RETRY } from './constants.js'\nimport {\n ThreadsAPIError,\n ThreadsNetworkError,\n ThreadsRateLimitError,\n ThreadsTimeoutError,\n ThreadsValidationError,\n toApiError,\n} from './errors.js'\nimport { type Logger, noopLogger, redactUrl } from './logger.js'\n\nexport type FetchLike = (url: string, init?: RequestInit) => Promise<Response>\n\nexport type HttpMethod = 'GET' | 'POST' | 'DELETE'\n\nexport interface RetryConfig {\n maxRetries: number\n initialDelayMs: number\n maxDelayMs: number\n backoffFactor: number\n}\n\nexport interface SendRequest {\n method: HttpMethod\n /** Fully-built URL (without `access_token`). */\n url: string\n /** Becomes the query string (GET) or form body (POST). */\n params?: Record<string, unknown>\n /** Added to the query (GET) or body (POST). Never logged. */\n accessToken?: string\n timeoutMs: number\n retry: RetryConfig | false\n logger?: Logger\n fetchImpl: FetchLike\n /** Optional caller-controlled cancellation, combined with the timeout. */\n signal?: AbortSignal\n}\n\n/**\n * Builds a URLSearchParams from a record, skipping `undefined`/`null` and\n * serializing arrays/objects to JSON (matching Graph API conventions).\n */\nexport function buildQuery(params: Record<string, unknown>): URLSearchParams {\n const query = new URLSearchParams()\n for (const [key, value] of Object.entries(params)) {\n if (value === undefined || value === null) continue\n if (Array.isArray(value) || (typeof value === 'object' && !(value instanceof Date))) {\n query.set(key, JSON.stringify(value))\n } else if (value instanceof Date) {\n query.set(key, String(Math.floor(value.getTime() / 1000)))\n } else {\n query.set(key, String(value))\n }\n }\n return query\n}\n\n/** Joins a base URL with a query string, preserving any existing query. */\nfunction appendQuery(url: string, query: URLSearchParams): string {\n const qs = query.toString()\n if (!qs) return url\n return url.includes('?') ? `${url}&${qs}` : `${url}?${qs}`\n}\n\n/** True for HTTP statuses worth retrying on idempotent requests. */\nexport function isRetryableStatus(status: number): boolean {\n return status === 429 || status >= 500\n}\n\n/**\n * Decides whether a failed attempt should be retried, given the request method.\n * See the module doc comment for the idempotency rationale.\n */\nexport function isRetryable(method: HttpMethod, error: unknown): boolean {\n if (error instanceof ThreadsRateLimitError) return true\n if (error instanceof ThreadsNetworkError) return true\n if (error instanceof ThreadsTimeoutError) return method === 'GET'\n if (error instanceof ThreadsAPIError) {\n return method === 'GET' && error.status != null && error.status >= 500\n }\n return false\n}\n\n/**\n * Computes the backoff delay before the next attempt. Honors a server-provided\n * `Retry-After` (capped), otherwise uses exponential backoff with equal jitter.\n */\nexport function backoffDelayMs(\n attempt: number,\n config: RetryConfig,\n retryAfterMs?: number,\n): number {\n if (retryAfterMs != null) return Math.min(retryAfterMs, config.maxDelayMs)\n const exponential = config.initialDelayMs * config.backoffFactor ** attempt\n const capped = Math.min(exponential, config.maxDelayMs)\n // Equal jitter: half fixed, half random — avoids thundering-herd retries.\n return capped / 2 + Math.random() * (capped / 2)\n}\n\nfunction sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms))\n}\n\nfunction safeJsonParse(text: string): unknown {\n try {\n return JSON.parse(text)\n } catch {\n return undefined\n }\n}\n\n/** Performs exactly one HTTP attempt (no retries). */\nasync function sendOnce<T>(req: SendRequest): Promise<T> {\n const { method, url, params = {}, accessToken, timeoutMs, fetchImpl, signal } = req\n\n const controller = new AbortController()\n let timedOut = false\n const onExternalAbort = () => controller.abort()\n if (signal) {\n if (signal.aborted) controller.abort()\n else signal.addEventListener('abort', onExternalAbort, { once: true })\n }\n const timer = setTimeout(() => {\n timedOut = true\n controller.abort()\n }, timeoutMs)\n\n try {\n const withToken = accessToken ? { ...params, access_token: accessToken } : params\n let finalUrl = url\n const init: RequestInit = { method, signal: controller.signal }\n\n if (method === 'GET' || method === 'DELETE') {\n finalUrl = appendQuery(url, buildQuery(withToken))\n } else {\n init.body = buildQuery(withToken)\n init.headers = { 'content-type': 'application/x-www-form-urlencoded' }\n }\n\n let response: Response\n try {\n response = await fetchImpl(finalUrl, init)\n } catch (cause) {\n if (controller.signal.aborted && timedOut) {\n throw new ThreadsTimeoutError(\n `Threads API request timed out after ${timeoutMs}ms`,\n { cause },\n )\n }\n throw new ThreadsNetworkError('Threads API request failed at the network layer', {\n cause,\n })\n }\n\n const text = await response.text()\n const body = text ? safeJsonParse(text) : undefined\n\n if (!response.ok) throw toApiError(response.status, body, response.headers)\n return body as T\n } finally {\n clearTimeout(timer)\n if (signal) signal.removeEventListener('abort', onExternalAbort)\n }\n}\n\n/**\n * Rejects a URL that already contains a query string, fragment, or whitespace\n * before we've had a chance to append our own query params.\n *\n * Every legitimate call site builds `url` as `{base}/{version}{path}`, where\n * `path` is a template like `/${mediaId}/replies` — never containing `?`/`#`\n * on its own. If one of those interpolated ids came from untrusted input\n * (e.g. a webhook payload passed straight through without validation) and\n * contained `?access_token=...` or similar, it would otherwise smuggle extra\n * query params — or redirect the request to a different path/endpoint\n * entirely — once we append the real query string. Failing closed here turns\n * that into a clear, immediate `ThreadsValidationError` instead of a\n * malformed or hijacked request.\n */\nfunction assertCleanUrl(url: string): void {\n if (/[?#\\s]/.test(url)) {\n throw new ThreadsValidationError(\n `Invalid request URL \"${url}\" — it contains \"?\", \"#\", or whitespace before query ` +\n 'params were added. This usually means an unvalidated id (e.g. from a webhook ' +\n 'payload) was interpolated into a resource path. Validate ids before passing them ' +\n 'to the SDK.',\n )\n }\n}\n\n/**\n * Sends a request with automatic retries. This is the only function that talks\n * to the network in the SDK.\n */\nexport async function send<T>(req: SendRequest): Promise<T> {\n assertCleanUrl(req.url)\n const logger = req.logger ?? noopLogger\n const retryConfig: RetryConfig =\n req.retry === false ? { ...DEFAULT_RETRY, maxRetries: 0 } : req.retry\n\n let attempt = 0\n for (;;) {\n const startedAt = Date.now()\n try {\n const result = await sendOnce<T>(req)\n logger.log('debug', 'threads.request.success', {\n method: req.method,\n url: redactUrl(req.url),\n attempt,\n durationMs: Date.now() - startedAt,\n })\n return result\n } catch (error) {\n const willRetry =\n isRetryable(req.method, error) &&\n attempt < retryConfig.maxRetries &&\n !req.signal?.aborted\n logger.log(willRetry ? 'warn' : 'error', 'threads.request.failure', {\n method: req.method,\n url: redactUrl(req.url),\n attempt,\n durationMs: Date.now() - startedAt,\n willRetry,\n error: error instanceof Error ? error.name : 'Unknown',\n })\n if (!willRetry) throw error\n\n const retryAfterMs =\n error instanceof ThreadsRateLimitError ? error.retryAfterMs : undefined\n await sleep(backoffDelayMs(attempt, retryConfig, retryAfterMs))\n attempt += 1\n }\n }\n}\n","/**\n * Shared contract between the client and its resource modules. Resources depend\n * only on this narrow interface (not the concrete `ThreadsClient`), which keeps\n * the dependency graph acyclic and makes resources trivial to unit test.\n */\n\nimport type { HttpMethod } from '../http.js'\n\nexport interface ResourceRequest {\n method: HttpMethod\n /** Path appended after the version segment, e.g. `/me/threads`. */\n path: string\n params?: Record<string, unknown>\n /** Optional per-call cancellation. */\n signal?: AbortSignal\n}\n\nexport interface ThreadsRequester {\n /** Default node id for user-scoped endpoints (the configured user, or `me`). */\n readonly userNode: string\n request<T>(req: ResourceRequest): Promise<T>\n}\n\n/** Joins requested `fields` into the comma-separated form the API expects. */\nexport function fieldsParam(fields: readonly string[] | undefined): string | undefined {\n return fields && fields.length > 0 ? fields.join(',') : undefined\n}\n","/** Insights — `GET /{media-id}/insights` and `GET /{user-id}/threads_insights`. */\n\nimport type { ThreadsRequester } from './base.js'\nimport type { InsightsResponse } from '../types.js'\n\n/** Documented media-level metrics. */\nexport type MediaMetric = 'views' | 'likes' | 'replies' | 'reposts' | 'quotes' | 'shares'\n\n/** Documented user-level metrics. */\nexport type UserMetric =\n | 'views'\n | 'likes'\n | 'replies'\n | 'reposts'\n | 'quotes'\n | 'clicks'\n | 'followers_count'\n | 'follower_demographics'\n\n/** Breakdown dimension for `follower_demographics`. */\nexport type DemographicBreakdown = 'country' | 'city' | 'age' | 'gender'\n\nconst DEFAULT_MEDIA_METRICS: MediaMetric[] = [\n 'views',\n 'likes',\n 'replies',\n 'reposts',\n 'quotes',\n 'shares',\n]\n\nexport interface MediaInsightsOptions {\n metrics?: MediaMetric[]\n signal?: AbortSignal\n}\n\nexport interface UserInsightsOptions {\n userId?: string\n metrics?: UserMetric[]\n since?: number | Date\n until?: number | Date\n /** Required by the API when requesting `follower_demographics`. */\n breakdown?: DemographicBreakdown\n signal?: AbortSignal\n}\n\nexport class InsightsResource {\n constructor(private readonly client: ThreadsRequester) {}\n\n /**\n * Returns engagement metrics for a single post.\n * Requires the `threads_manage_insights` permission.\n */\n media(mediaId: string, options: MediaInsightsOptions = {}): Promise<InsightsResponse> {\n const metrics = options.metrics ?? DEFAULT_MEDIA_METRICS\n return this.client.request<InsightsResponse>({\n method: 'GET',\n path: `/${mediaId}/insights`,\n params: { metric: metrics.join(',') },\n signal: options.signal,\n })\n }\n\n /**\n * Returns account-level metrics for a user.\n * Requires the `threads_manage_insights` permission.\n */\n user(options: UserInsightsOptions = {}): Promise<InsightsResponse> {\n const node = options.userId ?? this.client.userNode\n const metrics = options.metrics ?? ['views', 'followers_count']\n return this.client.request<InsightsResponse>({\n method: 'GET',\n path: `/${node}/threads_insights`,\n params: {\n metric: metrics.join(','),\n since: options.since,\n until: options.until,\n breakdown: options.breakdown,\n },\n signal: options.signal,\n })\n }\n}\n","/** Mentions — `GET /{user-id}/mentions`. */\n\nimport { fieldsParam, type ThreadsRequester } from './base.js'\nimport type { Paginated, ThreadsMedia } from '../types.js'\n\nconst DEFAULT_MENTION_FIELDS = [\n 'id',\n 'username',\n 'text',\n 'timestamp',\n 'permalink',\n 'media_type',\n] as const\n\nexport interface ListMentionsOptions {\n userId?: string\n fields?: string[]\n since?: number | Date\n until?: number | Date\n limit?: number\n before?: string\n after?: string\n signal?: AbortSignal\n}\n\nexport class MentionsResource {\n constructor(private readonly client: ThreadsRequester) {}\n\n /**\n * Lists posts that mention the user. Requires the mentions permission in\n * addition to `threads_basic`.\n */\n list(options: ListMentionsOptions = {}): Promise<Paginated<ThreadsMedia>> {\n const node = options.userId ?? this.client.userNode\n return this.client.request<Paginated<ThreadsMedia>>({\n method: 'GET',\n path: `/${node}/mentions`,\n params: {\n fields: fieldsParam(options.fields ?? DEFAULT_MENTION_FIELDS),\n since: options.since,\n until: options.until,\n limit: options.limit,\n before: options.before,\n after: options.after,\n },\n signal: options.signal,\n })\n }\n}\n","/** Retrieve & discover posts — `GET /{user-id}/threads` and `GET /{media-id}`. */\n\nimport { fieldsParam, type ThreadsRequester } from './base.js'\nimport type { Paginated, ThreadsMedia } from '../types.js'\n\nconst DEFAULT_MEDIA_FIELDS = [\n 'id',\n 'media_product_type',\n 'media_type',\n 'media_url',\n 'permalink',\n 'username',\n 'text',\n 'timestamp',\n 'shortcode',\n 'thumbnail_url',\n 'is_quote_post',\n] as const\n\nexport interface ListPostsOptions {\n /** User id to list; defaults to the configured user (or `me`). */\n userId?: string\n fields?: string[]\n /** Unix timestamp or `Date` lower bound. */\n since?: number | Date\n /** Unix timestamp or `Date` upper bound. */\n until?: number | Date\n limit?: number\n /** Pagination cursors (from a previous response's `paging`). */\n before?: string\n after?: string\n signal?: AbortSignal\n}\n\nexport interface GetPostOptions {\n fields?: string[]\n signal?: AbortSignal\n}\n\nexport class PostsResource {\n constructor(private readonly client: ThreadsRequester) {}\n\n /**\n * Lists a user's posts, most recent first. Cursor-paginated.\n * Requires the `threads_basic` permission.\n */\n list(options: ListPostsOptions = {}): Promise<Paginated<ThreadsMedia>> {\n const node = options.userId ?? this.client.userNode\n return this.client.request<Paginated<ThreadsMedia>>({\n method: 'GET',\n path: `/${node}/threads`,\n params: {\n fields: fieldsParam(options.fields ?? DEFAULT_MEDIA_FIELDS),\n since: options.since,\n until: options.until,\n limit: options.limit,\n before: options.before,\n after: options.after,\n },\n signal: options.signal,\n })\n }\n\n /**\n * Reads a single media object (post or reply) by id.\n * Requires the `threads_basic` permission.\n */\n get(mediaId: string, options: GetPostOptions = {}): Promise<ThreadsMedia> {\n return this.client.request<ThreadsMedia>({\n method: 'GET',\n path: `/${mediaId}`,\n params: { fields: fieldsParam(options.fields ?? DEFAULT_MEDIA_FIELDS) },\n signal: options.signal,\n })\n }\n}\n","/** Profile endpoint — `GET /me` / `GET /{user-id}`. */\n\nimport { fieldsParam, type ThreadsRequester } from './base.js'\nimport type { ThreadsProfile } from '../types.js'\n\n/** Fields requested by default when none are specified. */\nconst DEFAULT_PROFILE_FIELDS = [\n 'id',\n 'username',\n 'name',\n 'threads_profile_picture_url',\n 'threads_biography',\n 'is_verified',\n] as const\n\nexport interface GetProfileOptions {\n /** User id to read; defaults to the configured user (or `me`). */\n userId?: string\n /** Profile fields to return; defaults to all documented fields. */\n fields?: string[]\n signal?: AbortSignal\n}\n\nexport class ProfileResource {\n constructor(private readonly client: ThreadsRequester) {}\n\n /**\n * Returns profile information for a Threads user.\n * Requires the `threads_basic` permission.\n */\n get(options: GetProfileOptions = {}): Promise<ThreadsProfile> {\n const node = options.userId ?? this.client.userNode\n return this.client.request<ThreadsProfile>({\n method: 'GET',\n path: `/${node}`,\n params: { fields: fieldsParam(options.fields ?? DEFAULT_PROFILE_FIELDS) },\n signal: options.signal,\n })\n }\n}\n","/**\n * Publishing — the two-step container flow plus convenience wrappers.\n *\n * Flow: `createContainer()` → (optionally wait for processing) →\n * `publishContainer()`. The `publishText` / `publishImage` / `publishVideo` /\n * `publishCarousel` helpers run the whole flow for you.\n */\n\nimport { fieldsParam, type ThreadsRequester } from './base.js'\nimport { ThreadsError } from '../errors.js'\nimport type {\n ContainerRef,\n ContainerStatus,\n MediaContainerType,\n PublishingLimit,\n ReplyControl,\n} from '../types.js'\n\nexport interface CreateContainerInput {\n mediaType: MediaContainerType\n text?: string\n /** Required when `mediaType` is `IMAGE`. */\n imageUrl?: string\n /** Required when `mediaType` is `VIDEO`. */\n videoUrl?: string\n /** Mark this container as a carousel child (not published on its own). */\n isCarouselItem?: boolean\n /** Child container ids — required when `mediaType` is `CAROUSEL`. */\n children?: string[]\n /** Make this post a reply to the given media id. */\n replyToId?: string\n replyControl?: ReplyControl\n /** Accessibility description (max 1,000 chars). */\n altText?: string\n linkAttachment?: string\n locationId?: string\n quotePostId?: string\n topicTag?: string\n /** Attach a poll (TEXT posts only). 2–4 options. */\n pollAttachment?: PollAttachment\n /** Publishing user; defaults to the configured user (or `me`). */\n userId?: string\n signal?: AbortSignal\n}\n\nexport interface PollAttachment {\n optionA: string\n optionB: string\n optionC?: string\n optionD?: string\n}\n\nexport interface ContainerStatusResult {\n id: string\n status: ContainerStatus\n error_message?: string\n}\n\nexport interface WaitOptions {\n /** Poll until the container is ready before publishing. */\n waitForReady?: boolean\n pollIntervalMs?: number\n maxWaitMs?: number\n}\n\nexport type PublishImageInput = { imageUrl: string } & Omit<CreateContainerInput, 'mediaType'>\nexport type PublishVideoInput = { videoUrl: string } & Omit<CreateContainerInput, 'mediaType'>\n\nexport interface PublishCarouselInput {\n items: { imageUrl?: string; videoUrl?: string; altText?: string }[]\n text?: string\n replyControl?: ReplyControl\n userId?: string\n signal?: AbortSignal\n wait?: WaitOptions\n}\n\nfunction sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms))\n}\n\nexport class PublishingResource {\n constructor(private readonly client: ThreadsRequester) {}\n\n /** Step 1: create a media container. Returns its creation id. */\n createContainer(input: CreateContainerInput): Promise<ContainerRef> {\n const node = input.userId ?? this.client.userNode\n return this.client.request<ContainerRef>({\n method: 'POST',\n path: `/${node}/threads`,\n params: {\n media_type: input.mediaType,\n text: input.text,\n image_url: input.imageUrl,\n video_url: input.videoUrl,\n is_carousel_item: input.isCarouselItem,\n // The API expects a comma-separated list, not a JSON array.\n children: input.children?.join(','),\n reply_to_id: input.replyToId,\n reply_control: input.replyControl,\n alt_text: input.altText,\n link_attachment: input.linkAttachment,\n location_id: input.locationId,\n quote_post_id: input.quotePostId,\n topic_tag: input.topicTag,\n // The API expects a JSON object string with snake_case option keys.\n poll_attachment: input.pollAttachment\n ? JSON.stringify({\n option_a: input.pollAttachment.optionA,\n option_b: input.pollAttachment.optionB,\n option_c: input.pollAttachment.optionC,\n option_d: input.pollAttachment.optionD,\n })\n : undefined,\n },\n signal: input.signal,\n })\n }\n\n /** Step 2: publish a previously created container. Returns the post id. */\n publishContainer(\n creationId: string,\n options: { userId?: string; signal?: AbortSignal } = {},\n ): Promise<ContainerRef> {\n const node = options.userId ?? this.client.userNode\n return this.client.request<ContainerRef>({\n method: 'POST',\n path: `/${node}/threads_publish`,\n params: { creation_id: creationId },\n signal: options.signal,\n })\n }\n\n /** Reads a container's processing status. */\n getContainerStatus(\n containerId: string,\n options: { signal?: AbortSignal } = {},\n ): Promise<ContainerStatusResult> {\n return this.client.request<ContainerStatusResult>({\n method: 'GET',\n path: `/${containerId}`,\n params: { fields: fieldsParam(['id', 'status', 'error_message']) },\n signal: options.signal,\n })\n }\n\n /**\n * Creates a container, optionally waits for media processing, then publishes.\n * `waitForReady` defaults to `true` for `VIDEO`/`CAROUSEL` (which need\n * server-side processing) and `false` otherwise.\n */\n async createAndPublish(\n input: CreateContainerInput,\n wait: WaitOptions = {},\n ): Promise<ContainerRef> {\n const { id: creationId } = await this.createContainer(input)\n\n const shouldWait =\n wait.waitForReady ?? (input.mediaType === 'VIDEO' || input.mediaType === 'CAROUSEL')\n if (shouldWait) {\n await this.waitForContainer(creationId, wait, input.signal)\n }\n\n return this.publishContainer(creationId, {\n userId: input.userId,\n signal: input.signal,\n })\n }\n\n /** Convenience: publish a text-only post. */\n publishText(\n text: string,\n options: Omit<CreateContainerInput, 'mediaType' | 'text'> = {},\n ): Promise<ContainerRef> {\n return this.createAndPublish({ ...options, mediaType: 'TEXT', text })\n }\n\n /** Convenience: publish a text post with a poll (2–4 options). */\n publishPoll(\n text: string,\n poll: PollAttachment,\n options: Omit<CreateContainerInput, 'mediaType' | 'text' | 'pollAttachment'> = {},\n ): Promise<ContainerRef> {\n return this.createAndPublish({ ...options, mediaType: 'TEXT', text, pollAttachment: poll })\n }\n\n /** Delete a published post. Requires the `threads_delete` scope. */\n deletePost(postId: string, options: { signal?: AbortSignal } = {}): Promise<{ success: boolean }> {\n return this.client.request<{ success: boolean }>({\n method: 'DELETE',\n path: `/${postId}`,\n signal: options.signal,\n })\n }\n\n /** Convenience: publish a single image post. */\n publishImage(input: PublishImageInput): Promise<ContainerRef> {\n return this.createAndPublish({ ...input, mediaType: 'IMAGE' })\n }\n\n /** Convenience: publish a single video post (waits for processing). */\n publishVideo(input: PublishVideoInput): Promise<ContainerRef> {\n return this.createAndPublish({ ...input, mediaType: 'VIDEO' })\n }\n\n /**\n * Convenience: publish a carousel. Each item becomes a child container, then\n * a parent `CAROUSEL` container is created and published.\n */\n async publishCarousel(input: PublishCarouselInput): Promise<ContainerRef> {\n if (input.items.length < 2) {\n throw new ThreadsError('A carousel requires at least 2 items.')\n }\n const children = await Promise.all(\n input.items.map((item) =>\n this.createContainer({\n mediaType: item.videoUrl ? 'VIDEO' : 'IMAGE',\n imageUrl: item.imageUrl,\n videoUrl: item.videoUrl,\n altText: item.altText,\n isCarouselItem: true,\n userId: input.userId,\n signal: input.signal,\n }).then((ref) => ref.id),\n ),\n )\n\n return this.createAndPublish(\n {\n mediaType: 'CAROUSEL',\n children,\n text: input.text,\n replyControl: input.replyControl,\n userId: input.userId,\n signal: input.signal,\n },\n input.wait ?? {},\n )\n }\n\n /** Reads remaining publish/reply/delete quotas for the user. */\n getPublishingLimit(\n options: { userId?: string; signal?: AbortSignal } = {},\n ): Promise<PublishingLimit> {\n const node = options.userId ?? this.client.userNode\n return this.client.request<PublishingLimit>({\n method: 'GET',\n path: `/${node}/threads_publishing_limit`,\n params: {\n fields: fieldsParam([\n 'quota_usage',\n 'config',\n 'reply_quota_usage',\n 'reply_config',\n 'delete_quota_usage',\n 'delete_config',\n ]),\n },\n signal: options.signal,\n })\n }\n\n /** Polls a container until it is ready to publish, or throws on failure. */\n private async waitForContainer(\n containerId: string,\n wait: WaitOptions,\n signal?: AbortSignal,\n ): Promise<void> {\n const pollIntervalMs = wait.pollIntervalMs ?? 2_000\n const maxWaitMs = wait.maxWaitMs ?? 60_000\n const deadline = Date.now() + maxWaitMs\n\n for (;;) {\n const { status, error_message } = await this.getContainerStatus(containerId, { signal })\n if (status === 'FINISHED' || status === 'PUBLISHED') return\n if (status === 'ERROR' || status === 'EXPIRED') {\n throw new ThreadsError(\n `Container ${containerId} failed to process: ${status}${\n error_message ? ` (${error_message})` : ''\n }`,\n )\n }\n if (Date.now() + pollIntervalMs > deadline) {\n throw new ThreadsError(\n `Container ${containerId} not ready after ${maxWaitMs}ms (status: ${status}).`,\n )\n }\n await sleep(pollIntervalMs)\n }\n }\n}\n","/**\n * Replies & moderation.\n *\n * Reading: `GET /{media}/replies` (top-level) and `GET /{media}/conversation`\n * (flattened thread). Moderation: hide/unhide, plus the reply-approval queue.\n * Publishing a reply reuses the publishing flow with `reply_to_id` set.\n */\n\nimport { fieldsParam, type ThreadsRequester } from './base.js'\nimport type { ContainerRef, Paginated, SuccessResponse, ThreadsReply } from '../types.js'\nimport type { PublishingResource } from './publishing.js'\n\nconst DEFAULT_REPLY_FIELDS = [\n 'id',\n 'text',\n 'username',\n 'timestamp',\n 'media_type',\n 'permalink',\n 'has_replies',\n 'is_reply',\n 'hide_status',\n] as const\n\nexport interface ListRepliesOptions {\n fields?: string[]\n /** Reverse chronological order. */\n reverse?: boolean\n signal?: AbortSignal\n}\n\nexport interface ListPendingRepliesOptions {\n fields?: string[]\n reverse?: boolean\n /** Filter by approval state. */\n approvalStatus?: 'pending' | 'ignored'\n signal?: AbortSignal\n}\n\nexport class RepliesResource {\n constructor(\n private readonly client: ThreadsRequester,\n private readonly publishing: PublishingResource,\n ) {}\n\n /**\n * Lists the top-level replies to a post.\n * Requires `threads_read_replies` (or `threads_manage_replies`).\n */\n list(mediaId: string, options: ListRepliesOptions = {}): Promise<Paginated<ThreadsReply>> {\n return this.client.request<Paginated<ThreadsReply>>({\n method: 'GET',\n path: `/${mediaId}/replies`,\n params: {\n fields: fieldsParam(options.fields ?? DEFAULT_REPLY_FIELDS),\n reverse: options.reverse,\n },\n signal: options.signal,\n })\n }\n\n /**\n * Lists the full conversation (all nested replies) under a post.\n * Requires `threads_read_replies` (or `threads_manage_replies`).\n */\n conversation(\n mediaId: string,\n options: ListRepliesOptions = {},\n ): Promise<Paginated<ThreadsReply>> {\n return this.client.request<Paginated<ThreadsReply>>({\n method: 'GET',\n path: `/${mediaId}/conversation`,\n params: {\n fields: fieldsParam(options.fields ?? DEFAULT_REPLY_FIELDS),\n reverse: options.reverse,\n },\n signal: options.signal,\n })\n }\n\n /**\n * Publishes a reply to an existing post. Requires `threads_content_publish`\n * (and `threads_manage_replies` for replies you don't own).\n */\n publish(\n replyToId: string,\n text: string,\n options: { userId?: string; signal?: AbortSignal } = {},\n ): Promise<ContainerRef> {\n return this.publishing.publishText(text, { replyToId, ...options })\n }\n\n /** Hides a reply. Requires `threads_manage_replies`. */\n hide(replyId: string, options: { signal?: AbortSignal } = {}): Promise<SuccessResponse> {\n return this.setHidden(replyId, true, options)\n }\n\n /** Unhides a previously hidden reply. Requires `threads_manage_replies`. */\n unhide(replyId: string, options: { signal?: AbortSignal } = {}): Promise<SuccessResponse> {\n return this.setHidden(replyId, false, options)\n }\n\n /**\n * Lists replies awaiting approval (when reply approvals are enabled).\n * Requires `threads_manage_replies`.\n */\n listPending(\n mediaId: string,\n options: ListPendingRepliesOptions = {},\n ): Promise<Paginated<ThreadsReply>> {\n return this.client.request<Paginated<ThreadsReply>>({\n method: 'GET',\n path: `/${mediaId}/pending_replies`,\n params: {\n fields: fieldsParam(options.fields ?? DEFAULT_REPLY_FIELDS),\n reverse: options.reverse,\n approval_status: options.approvalStatus,\n },\n signal: options.signal,\n })\n }\n\n /**\n * Approves or rejects a pending reply. Requires `threads_manage_replies`.\n */\n managePending(\n replyId: string,\n approve: boolean,\n options: { signal?: AbortSignal } = {},\n ): Promise<SuccessResponse> {\n return this.client.request<SuccessResponse>({\n method: 'POST',\n path: `/${replyId}/manage_pending_reply`,\n params: { approve },\n signal: options.signal,\n })\n }\n\n private setHidden(\n replyId: string,\n hide: boolean,\n options: { signal?: AbortSignal },\n ): Promise<SuccessResponse> {\n return this.client.request<SuccessResponse>({\n method: 'POST',\n path: `/${replyId}/manage_reply`,\n params: { hide },\n signal: options.signal,\n })\n }\n}\n","/** Keyword search — `GET /keyword_search`. */\n\nimport { fieldsParam, type ThreadsRequester } from './base.js'\nimport type { MediaContainerType, Paginated, ThreadsMedia } from '../types.js'\n\nconst DEFAULT_SEARCH_FIELDS = [\n 'id',\n 'username',\n 'text',\n 'timestamp',\n 'permalink',\n 'media_type',\n 'has_replies',\n 'is_quote_post',\n 'is_reply',\n] as const\n\nexport interface KeywordSearchOptions {\n /** `TOP` (default) ranks by relevance; `RECENT` is reverse-chronological. */\n searchType?: 'TOP' | 'RECENT'\n /** `KEYWORD` (default) or `TAG`. */\n searchMode?: 'KEYWORD' | 'TAG'\n /** Restrict to a media type. */\n mediaType?: Extract<MediaContainerType, 'TEXT' | 'IMAGE' | 'VIDEO'>\n since?: number | Date\n until?: number | Date\n /** Default 25, max 100. */\n limit?: number\n /** Exact username to filter results by author. */\n authorUsername?: string\n fields?: string[]\n signal?: AbortSignal\n}\n\nexport class SearchResource {\n constructor(private readonly client: ThreadsRequester) {}\n\n /**\n * Searches public Threads posts by keyword or tag. The `owner` field is never\n * returned for search results. Requires the `threads_keyword_search`\n * permission (plus `threads_basic`).\n */\n keyword(query: string, options: KeywordSearchOptions = {}): Promise<Paginated<ThreadsMedia>> {\n return this.client.request<Paginated<ThreadsMedia>>({\n method: 'GET',\n path: '/keyword_search',\n params: {\n q: query,\n search_type: options.searchType,\n search_mode: options.searchMode,\n media_type: options.mediaType,\n since: options.since,\n until: options.until,\n limit: options.limit,\n author_username: options.authorUsername,\n fields: fieldsParam(options.fields ?? DEFAULT_SEARCH_FIELDS),\n },\n signal: options.signal,\n })\n }\n}\n","/**\n * `ThreadsClient` — the typed entry point. Holds resolved configuration and\n * exposes resource namespaces. One client instance is bound to one access\n * token; create a new instance (or use {@link ThreadsClient.withToken}) per\n * user/token.\n */\n\nimport {\n DEFAULT_API_VERSION,\n DEFAULT_BASE_URL,\n DEFAULT_RETRY,\n DEFAULT_TIMEOUT_MS,\n} from './constants.js'\nimport { ThreadsValidationError } from './errors.js'\nimport { type FetchLike, type RetryConfig, send } from './http.js'\nimport { type Logger, noopLogger } from './logger.js'\nimport { type ResourceRequest, type ThreadsRequester } from './resources/base.js'\nimport { InsightsResource } from './resources/insights.js'\nimport { MentionsResource } from './resources/mentions.js'\nimport { PostsResource } from './resources/posts.js'\nimport { ProfileResource } from './resources/profile.js'\nimport { PublishingResource } from './resources/publishing.js'\nimport { RepliesResource } from './resources/replies.js'\nimport { SearchResource } from './resources/search.js'\n\nexport interface ThreadsClientConfig {\n /** A Threads user access token (short- or long-lived). */\n accessToken: string\n /**\n * Default node id for user-scoped endpoints. Defaults to `me`, which resolves\n * to the token's owner.\n */\n userId?: string\n /** Graph host override. Defaults to `https://graph.threads.net`. */\n baseUrl?: string\n /** API version. Defaults to `v1.0`. */\n version?: string\n /** Per-request timeout in ms. Defaults to 30000. */\n timeoutMs?: number\n /**\n * Retry policy for idempotent requests. Pass `false` to disable retries, or a\n * partial object to override individual fields.\n */\n retry?: Partial<RetryConfig> | false\n /** Logging hook. Tokens/secrets are always redacted before logging. */\n logger?: Logger\n /** Custom fetch (for tests or non-standard runtimes). Defaults to global. */\n fetch?: FetchLike\n}\n\ninterface ResolvedConfig {\n accessToken: string\n userId: string\n baseUrl: string\n version: string\n timeoutMs: number\n retry: RetryConfig | false\n logger: Logger\n fetch: FetchLike\n}\n\nfunction resolveConfig(config: ThreadsClientConfig): ResolvedConfig {\n if (!config.accessToken) {\n throw new ThreadsValidationError('`accessToken` is required to create a ThreadsClient.')\n }\n const fetchImpl = config.fetch ?? (globalThis.fetch as FetchLike | undefined)\n if (!fetchImpl) {\n throw new ThreadsValidationError(\n 'No fetch implementation available. Pass `fetch` or run on Node 18+ / a runtime with global fetch.',\n )\n }\n return {\n accessToken: config.accessToken,\n userId: config.userId ?? 'me',\n baseUrl: (config.baseUrl ?? DEFAULT_BASE_URL).replace(/\\/+$/, ''),\n version: config.version ?? DEFAULT_API_VERSION,\n timeoutMs: config.timeoutMs ?? DEFAULT_TIMEOUT_MS,\n retry: config.retry === false ? false : { ...DEFAULT_RETRY, ...config.retry },\n logger: config.logger ?? noopLogger,\n fetch: fetchImpl,\n }\n}\n\nexport class ThreadsClient implements ThreadsRequester {\n readonly profile: ProfileResource\n readonly posts: PostsResource\n readonly publishing: PublishingResource\n readonly replies: RepliesResource\n readonly insights: InsightsResource\n readonly mentions: MentionsResource\n readonly search: SearchResource\n\n private readonly config: ResolvedConfig\n\n constructor(config: ThreadsClientConfig) {\n this.config = resolveConfig(config)\n\n this.profile = new ProfileResource(this)\n this.posts = new PostsResource(this)\n this.publishing = new PublishingResource(this)\n this.replies = new RepliesResource(this, this.publishing)\n this.insights = new InsightsResource(this)\n this.mentions = new MentionsResource(this)\n this.search = new SearchResource(this)\n }\n\n /** Default node id for user-scoped endpoints. */\n get userNode(): string {\n return this.config.userId\n }\n\n /**\n * Low-level escape hatch: issue a request to any Threads endpoint with full\n * typing of the response. Prefer the resource methods; use this for endpoints\n * the SDK doesn't model yet.\n */\n request<T>(req: ResourceRequest): Promise<T> {\n return send<T>({\n method: req.method,\n url: `${this.config.baseUrl}/${this.config.version}${req.path}`,\n params: req.params,\n accessToken: this.config.accessToken,\n timeoutMs: this.config.timeoutMs,\n retry: this.config.retry,\n logger: this.config.logger,\n fetchImpl: this.config.fetch,\n signal: req.signal,\n })\n }\n\n /** Returns a new client that shares this config but uses a different token. */\n withToken(accessToken: string): ThreadsClient {\n return new ThreadsClient({ ...this.config, accessToken })\n }\n}\n","/**\n * OAuth 2.0 helpers for the Threads authorization-code flow.\n *\n * These are standalone functions (not methods on the client) so a host app that\n * only handles the auth handshake can import them without pulling in the rest\n * of the SDK. Token-exchange calls must run server-side: they require the app\n * secret, which must never reach a browser.\n */\n\nimport {\n AUTHORIZATION_BASE_URL,\n DEFAULT_BASE_URL,\n DEFAULT_TIMEOUT_MS,\n type ThreadsScope,\n} from './constants.js'\nimport { ThreadsValidationError } from './errors.js'\nimport { buildQuery, type FetchLike, send } from './http.js'\nimport type { Logger } from './logger.js'\n\n/** Options shared by the server-side token-exchange calls. */\ninterface ExchangeBaseOptions {\n /** Defaults to the global `fetch`. */\n fetch?: FetchLike\n /** Graph host override (defaults to `https://graph.threads.net`). */\n baseUrl?: string\n /** Per-request timeout in ms. */\n timeoutMs?: number\n /** Optional logging hook (token values are always redacted). */\n logger?: Logger\n}\n\nexport interface AuthorizationUrlOptions {\n clientId: string\n redirectUri: string\n /** Requested scopes. `threads_basic` is required by the API. */\n scopes: ThreadsScope[]\n /** Opaque CSRF token echoed back to your redirect URI. Strongly recommended. */\n state?: string\n /** Authorization host override (defaults to `https://threads.net`). */\n baseUrl?: string\n}\n\nexport interface ShortLivedTokenResponse {\n access_token: string\n user_id: string\n}\n\nexport interface LongLivedTokenResponse {\n access_token: string\n token_type: string\n /** Seconds until expiry (~60 days). */\n expires_in: number\n}\n\nfunction resolveFetch(fetchImpl?: FetchLike): FetchLike {\n const impl = fetchImpl ?? (globalThis.fetch as FetchLike | undefined)\n if (!impl) {\n throw new ThreadsValidationError(\n 'No fetch implementation available. Pass `fetch` explicitly or run on a runtime with global fetch (Node 18+).',\n )\n }\n return impl\n}\n\n/**\n * Builds the URL to redirect a user to in order to grant your app access.\n *\n * @example\n * ```ts\n * const url = getAuthorizationUrl({\n * clientId: process.env.THREADS_APP_ID!,\n * redirectUri: 'https://app.example.com/auth/callback',\n * scopes: ['threads_basic', 'threads_content_publish'],\n * state: csrfToken,\n * })\n * ```\n */\nexport function getAuthorizationUrl(options: AuthorizationUrlOptions): string {\n if (options.scopes.length === 0) {\n throw new ThreadsValidationError('At least one scope is required (threads_basic).')\n }\n const base = options.baseUrl ?? AUTHORIZATION_BASE_URL\n const query = buildQuery({\n client_id: options.clientId,\n redirect_uri: options.redirectUri,\n response_type: 'code',\n scope: options.scopes.join(','),\n state: options.state,\n })\n return `${base}/oauth/authorize?${query.toString()}`\n}\n\n/**\n * Exchanges an authorization `code` for a short-lived access token. Server-side\n * only (requires the app secret).\n */\nexport async function exchangeCodeForToken(\n options: ExchangeBaseOptions & {\n clientId: string\n clientSecret: string\n code: string\n redirectUri: string\n },\n): Promise<ShortLivedTokenResponse> {\n const base = options.baseUrl ?? DEFAULT_BASE_URL\n return send<ShortLivedTokenResponse>({\n method: 'POST',\n url: `${base}/oauth/access_token`,\n params: {\n client_id: options.clientId,\n client_secret: options.clientSecret,\n grant_type: 'authorization_code',\n redirect_uri: options.redirectUri,\n code: options.code,\n },\n timeoutMs: options.timeoutMs ?? DEFAULT_TIMEOUT_MS,\n retry: false, // code is single-use; never replay it\n logger: options.logger,\n fetchImpl: resolveFetch(options.fetch),\n })\n}\n\n/**\n * Exchanges a short-lived token for a long-lived (~60 day) token. Server-side\n * only (requires the app secret).\n */\nexport async function exchangeForLongLivedToken(\n options: ExchangeBaseOptions & {\n clientSecret: string\n shortLivedToken: string\n },\n): Promise<LongLivedTokenResponse> {\n const base = options.baseUrl ?? DEFAULT_BASE_URL\n return send<LongLivedTokenResponse>({\n method: 'GET',\n url: `${base}/access_token`,\n params: {\n grant_type: 'th_exchange_token',\n client_secret: options.clientSecret,\n },\n accessToken: options.shortLivedToken,\n timeoutMs: options.timeoutMs ?? DEFAULT_TIMEOUT_MS,\n retry: false,\n logger: options.logger,\n fetchImpl: resolveFetch(options.fetch),\n })\n}\n\n/**\n * Refreshes a long-lived token, extending it ~60 days. The token must be at\n * least 24 hours old and unexpired. No app secret required.\n */\nexport async function refreshLongLivedToken(\n options: ExchangeBaseOptions & {\n longLivedToken: string\n },\n): Promise<LongLivedTokenResponse> {\n const base = options.baseUrl ?? DEFAULT_BASE_URL\n return send<LongLivedTokenResponse>({\n method: 'GET',\n url: `${base}/refresh_access_token`,\n params: { grant_type: 'th_refresh_token' },\n accessToken: options.longLivedToken,\n timeoutMs: options.timeoutMs ?? DEFAULT_TIMEOUT_MS,\n retry: false,\n logger: options.logger,\n fetchImpl: resolveFetch(options.fetch),\n })\n}\n"]}
|
package/docs/README.md
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# tredi-sdk Documentation
|
|
2
|
+
|
|
3
|
+
| Doc | What's inside |
|
|
4
|
+
|---|---|
|
|
5
|
+
| [API Reference](./api-reference.md) | Every public export — client, resources, OAuth, errors, logging, types — with signatures, options, required scopes, and the endpoint each maps to. |
|
|
6
|
+
| [Architecture](./architecture.md) | Module map, request lifecycle, retry/idempotency model, error mapping, security model, and the key design decisions & trade-offs (with diagrams). |
|
|
7
|
+
| [Guides](./guides.md) | Task recipes: OAuth, token refresh, publishing, reply automation, analytics, monitoring, error/rate-limit handling, pagination, custom runtimes, logging. |
|
|
8
|
+
|
|
9
|
+
**New here?** Start with the [project README](../README.md) for install + quick
|
|
10
|
+
start, then follow a recipe in the [Guides](./guides.md). Runnable code lives in
|
|
11
|
+
[../examples](../examples).
|
|
12
|
+
|
|
13
|
+
**Reference:** this SDK models the official
|
|
14
|
+
[Meta Threads API](https://developers.facebook.com/docs/threads/).
|
|
@@ -0,0 +1,452 @@
|
|
|
1
|
+
# tredi-sdk — API Reference
|
|
2
|
+
|
|
3
|
+
Complete reference for every public export. For task-oriented walkthroughs see
|
|
4
|
+
[guides.md](./guides.md); for internals see [architecture.md](./architecture.md).
|
|
5
|
+
|
|
6
|
+
All endpoints target the Threads Graph API (`graph.threads.net`, `v1.0`). Every
|
|
7
|
+
call requires the `threads_basic` permission in addition to the scope noted per
|
|
8
|
+
method.
|
|
9
|
+
|
|
10
|
+
## Contents
|
|
11
|
+
|
|
12
|
+
- [ThreadsClient](#threadsclient)
|
|
13
|
+
- [Configuration](#configuration)
|
|
14
|
+
- [Resource: profile](#resource-profile)
|
|
15
|
+
- [Resource: posts](#resource-posts)
|
|
16
|
+
- [Resource: publishing](#resource-publishing)
|
|
17
|
+
- [Resource: replies](#resource-replies)
|
|
18
|
+
- [Resource: insights](#resource-insights)
|
|
19
|
+
- [Resource: mentions](#resource-mentions)
|
|
20
|
+
- [Resource: search](#resource-search)
|
|
21
|
+
- [OAuth helpers](#oauth-helpers)
|
|
22
|
+
- [Errors](#errors)
|
|
23
|
+
- [Logging](#logging)
|
|
24
|
+
- [Constants & scopes](#constants--scopes)
|
|
25
|
+
- [Types](#types)
|
|
26
|
+
|
|
27
|
+
---
|
|
28
|
+
|
|
29
|
+
## ThreadsClient
|
|
30
|
+
|
|
31
|
+
```ts
|
|
32
|
+
import { ThreadsClient } from 'tredi-sdk'
|
|
33
|
+
const threads = new ThreadsClient({ accessToken: process.env.THREADS_TOKEN! })
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
One instance is bound to one access token. It exposes seven resource
|
|
37
|
+
namespaces plus a low-level escape hatch.
|
|
38
|
+
|
|
39
|
+
| Member | Type | Description |
|
|
40
|
+
|---|---|---|
|
|
41
|
+
| `profile` | `ProfileResource` | Read user profiles |
|
|
42
|
+
| `posts` | `PostsResource` | List/read posts |
|
|
43
|
+
| `publishing` | `PublishingResource` | Create & publish posts |
|
|
44
|
+
| `replies` | `RepliesResource` | Read & moderate replies |
|
|
45
|
+
| `insights` | `InsightsResource` | Media & account metrics |
|
|
46
|
+
| `mentions` | `MentionsResource` | Posts mentioning the user |
|
|
47
|
+
| `search` | `SearchResource` | Keyword/tag search |
|
|
48
|
+
| `userNode` | `string` (getter) | The default node id (`me` or configured `userId`) |
|
|
49
|
+
|
|
50
|
+
### `request<T>(req)`
|
|
51
|
+
|
|
52
|
+
Low-level escape hatch for endpoints the SDK doesn't model. Returns the parsed
|
|
53
|
+
JSON typed as `T`. Goes through the same timeout/retry/error/logging pipeline.
|
|
54
|
+
|
|
55
|
+
```ts
|
|
56
|
+
request<T>(req: {
|
|
57
|
+
method: 'GET' | 'POST'
|
|
58
|
+
path: string // appended after the version, e.g. '/me/threads'
|
|
59
|
+
params?: Record<string, unknown> // query (GET) or form body (POST)
|
|
60
|
+
signal?: AbortSignal
|
|
61
|
+
}): Promise<T>
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
### `withToken(accessToken)`
|
|
65
|
+
|
|
66
|
+
Returns a **new** client that shares this configuration but uses a different
|
|
67
|
+
token. Useful for multi-tenant apps — never mutate a shared client's token.
|
|
68
|
+
|
|
69
|
+
```ts
|
|
70
|
+
const scoped = threads.withToken(userToken)
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
---
|
|
74
|
+
|
|
75
|
+
## Configuration
|
|
76
|
+
|
|
77
|
+
```ts
|
|
78
|
+
new ThreadsClient(config: ThreadsClientConfig)
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
| Field | Type | Default | Description |
|
|
82
|
+
|---|---|---|---|
|
|
83
|
+
| `accessToken` | `string` | — (**required**) | Threads user access token |
|
|
84
|
+
| `userId` | `string` | `'me'` | Default node for user-scoped calls |
|
|
85
|
+
| `baseUrl` | `string` | `https://graph.threads.net` | Graph host override |
|
|
86
|
+
| `version` | `string` | `v1.0` | API version |
|
|
87
|
+
| `timeoutMs` | `number` | `30000` | Per-request timeout |
|
|
88
|
+
| `retry` | `Partial<RetryConfig> \| false` | `{maxRetries:2, initialDelayMs:500, maxDelayMs:8000, backoffFactor:2}` | Retry policy, or `false` to disable |
|
|
89
|
+
| `logger` | `Logger` | `noopLogger` | Structured log sink (secrets redacted) |
|
|
90
|
+
| `fetch` | `FetchLike` | `globalThis.fetch` | Custom fetch (tests/edge runtimes) |
|
|
91
|
+
|
|
92
|
+
Throws `ThreadsValidationError` if `accessToken` is empty or no `fetch` is available.
|
|
93
|
+
|
|
94
|
+
---
|
|
95
|
+
|
|
96
|
+
## Resource: profile
|
|
97
|
+
|
|
98
|
+
### `profile.get(options?)` → `Promise<ThreadsProfile>`
|
|
99
|
+
|
|
100
|
+
Scope: `threads_basic`. Endpoint: `GET /{user}`.
|
|
101
|
+
|
|
102
|
+
| Option | Type | Default |
|
|
103
|
+
|---|---|---|
|
|
104
|
+
| `userId` | `string` | configured user / `me` |
|
|
105
|
+
| `fields` | `string[]` | `id, username, name, threads_profile_picture_url, threads_biography, is_verified` |
|
|
106
|
+
| `signal` | `AbortSignal` | — |
|
|
107
|
+
|
|
108
|
+
```ts
|
|
109
|
+
const me = await threads.profile.get()
|
|
110
|
+
const other = await threads.profile.get({ userId: '178…', fields: ['id', 'username'] })
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
---
|
|
114
|
+
|
|
115
|
+
## Resource: posts
|
|
116
|
+
|
|
117
|
+
### `posts.list(options?)` → `Promise<Paginated<ThreadsMedia>>`
|
|
118
|
+
|
|
119
|
+
Scope: `threads_basic`. Endpoint: `GET /{user}/threads`. Cursor-paginated.
|
|
120
|
+
|
|
121
|
+
| Option | Type | Notes |
|
|
122
|
+
|---|---|---|
|
|
123
|
+
| `userId` | `string` | defaults to configured user / `me` |
|
|
124
|
+
| `fields` | `string[]` | defaults to a common subset |
|
|
125
|
+
| `since` / `until` | `number \| Date` | range filter (unix seconds or `Date`) |
|
|
126
|
+
| `limit` | `number` | page size |
|
|
127
|
+
| `before` / `after` | `string` | pagination cursors |
|
|
128
|
+
| `signal` | `AbortSignal` | — |
|
|
129
|
+
|
|
130
|
+
### `posts.get(mediaId, options?)` → `Promise<ThreadsMedia>`
|
|
131
|
+
|
|
132
|
+
Scope: `threads_basic`. Endpoint: `GET /{media-id}`.
|
|
133
|
+
|
|
134
|
+
```ts
|
|
135
|
+
const page = await threads.posts.list({ limit: 25 })
|
|
136
|
+
const post = await threads.posts.get(page.data[0].id!, { fields: ['id', 'text', 'permalink'] })
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
---
|
|
140
|
+
|
|
141
|
+
## Resource: publishing
|
|
142
|
+
|
|
143
|
+
Two-step flow: **create container → publish**. Convenience wrappers run both.
|
|
144
|
+
|
|
145
|
+
### `publishing.createContainer(input)` → `Promise<ContainerRef>`
|
|
146
|
+
|
|
147
|
+
Scope: `threads_content_publish`. Endpoint: `POST /{user}/threads`.
|
|
148
|
+
|
|
149
|
+
| Field | Type | Notes |
|
|
150
|
+
|---|---|---|
|
|
151
|
+
| `mediaType` | `'TEXT' \| 'IMAGE' \| 'VIDEO' \| 'CAROUSEL'` | **required** |
|
|
152
|
+
| `text` | `string` | post body |
|
|
153
|
+
| `imageUrl` | `string` | required for `IMAGE` |
|
|
154
|
+
| `videoUrl` | `string` | required for `VIDEO` |
|
|
155
|
+
| `isCarouselItem` | `boolean` | mark as a carousel child |
|
|
156
|
+
| `children` | `string[]` | child container ids (for `CAROUSEL`) |
|
|
157
|
+
| `replyToId` | `string` | make this a reply |
|
|
158
|
+
| `replyControl` | `ReplyControl` | who can reply |
|
|
159
|
+
| `altText` | `string` | accessibility text (≤1000 chars) |
|
|
160
|
+
| `linkAttachment` | `string` | attached URL |
|
|
161
|
+
| `locationId` | `string` | tagged location |
|
|
162
|
+
| `quotePostId` | `string` | quoted post |
|
|
163
|
+
| `topicTag` | `string` | topic tag |
|
|
164
|
+
| `userId` | `string` | publishing user |
|
|
165
|
+
| `signal` | `AbortSignal` | — |
|
|
166
|
+
|
|
167
|
+
### `publishing.publishContainer(creationId, options?)` → `Promise<ContainerRef>`
|
|
168
|
+
|
|
169
|
+
Scope: `threads_content_publish`. Endpoint: `POST /{user}/threads_publish`.
|
|
170
|
+
`options`: `{ userId?, signal? }`.
|
|
171
|
+
|
|
172
|
+
### `publishing.getContainerStatus(containerId, signal?)` → `Promise<ContainerStatusResult>`
|
|
173
|
+
|
|
174
|
+
Endpoint: `GET /{container-id}?fields=status,error_message`. Returns
|
|
175
|
+
`{ id, status, error_message? }` where `status` is one of
|
|
176
|
+
`EXPIRED | ERROR | FINISHED | IN_PROGRESS | PUBLISHED`.
|
|
177
|
+
|
|
178
|
+
### `publishing.createAndPublish(input, wait?)` → `Promise<ContainerRef>`
|
|
179
|
+
|
|
180
|
+
Creates, optionally polls until ready, then publishes.
|
|
181
|
+
|
|
182
|
+
`wait`: `{ waitForReady?: boolean; pollIntervalMs?: number; maxWaitMs?: number }`.
|
|
183
|
+
`waitForReady` defaults to `true` for `VIDEO`/`CAROUSEL`, `false` otherwise.
|
|
184
|
+
Throws `ThreadsError` if the container reports `ERROR`/`EXPIRED` or doesn't become
|
|
185
|
+
ready before `maxWaitMs` (default 60000; poll interval default 2000).
|
|
186
|
+
|
|
187
|
+
### Convenience wrappers
|
|
188
|
+
|
|
189
|
+
| Method | Signature |
|
|
190
|
+
|---|---|
|
|
191
|
+
| `publishText` | `(text, options?) => Promise<ContainerRef>` |
|
|
192
|
+
| `publishPoll` | `(text, poll, options?) => Promise<ContainerRef>` |
|
|
193
|
+
| `publishImage` | `({ imageUrl, ...opts }) => Promise<ContainerRef>` |
|
|
194
|
+
| `publishVideo` | `({ videoUrl, ...opts }) => Promise<ContainerRef>` (waits for processing) |
|
|
195
|
+
| `publishCarousel` | `({ items, text?, replyControl?, userId?, signal?, wait? }) => Promise<ContainerRef>` |
|
|
196
|
+
|
|
197
|
+
`publishCarousel` requires ≥2 items; each item is `{ imageUrl?, videoUrl?, altText? }`.
|
|
198
|
+
|
|
199
|
+
`publishPoll`'s `poll` argument is `{ optionA, optionB, optionC?, optionD? }` (2–4 options,
|
|
200
|
+
TEXT posts only). Serialized as `poll_attachment`, a JSON object with snake_case keys.
|
|
201
|
+
|
|
202
|
+
### `publishing.getPublishingLimit(options?)` → `Promise<PublishingLimit>`
|
|
203
|
+
|
|
204
|
+
Endpoint: `GET /{user}/threads_publishing_limit`. Reports remaining quota
|
|
205
|
+
(250 posts / 1000 replies / 100 deletes per 24h). `options`: `{ userId?, signal? }`.
|
|
206
|
+
|
|
207
|
+
### `publishing.deletePost(postId, options?)` → `Promise<{ success: boolean }>`
|
|
208
|
+
|
|
209
|
+
Scope: `threads_delete`. Endpoint: `DELETE /{post-id}`. `options`: `{ signal? }`.
|
|
210
|
+
|
|
211
|
+
```ts
|
|
212
|
+
const post = await threads.publishing.publishText('gm ☕')
|
|
213
|
+
await threads.publishing.publishPoll('Coffee or tea?', { optionA: 'Coffee', optionB: 'Tea' })
|
|
214
|
+
await threads.publishing.publishVideo({ videoUrl: 'https://…/v.mp4', text: 'demo' })
|
|
215
|
+
await threads.publishing.publishCarousel({ items: [{ imageUrl: 'a' }, { imageUrl: 'b' }] })
|
|
216
|
+
await threads.publishing.deletePost(post.id)
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
---
|
|
220
|
+
|
|
221
|
+
## Resource: replies
|
|
222
|
+
|
|
223
|
+
### `replies.list(mediaId, options?)` → `Promise<Paginated<ThreadsReply>>`
|
|
224
|
+
|
|
225
|
+
Scope: `threads_read_replies` (or `threads_manage_replies`).
|
|
226
|
+
Endpoint: `GET /{media-id}/replies` (top-level replies).
|
|
227
|
+
`options`: `{ fields?, reverse?, signal? }`.
|
|
228
|
+
|
|
229
|
+
### `replies.conversation(mediaId, options?)` → `Promise<Paginated<ThreadsReply>>`
|
|
230
|
+
|
|
231
|
+
Endpoint: `GET /{media-id}/conversation` (full nested thread). Same options.
|
|
232
|
+
|
|
233
|
+
### `replies.publish(replyToId, text, options?)` → `Promise<ContainerRef>`
|
|
234
|
+
|
|
235
|
+
Scope: `threads_content_publish` (+ `threads_manage_replies` for others' posts).
|
|
236
|
+
Creates a reply container with `reply_to_id` and publishes it.
|
|
237
|
+
`options`: `{ userId?, signal? }`.
|
|
238
|
+
|
|
239
|
+
### `replies.hide(replyId, signal?)` / `replies.unhide(replyId, signal?)` → `Promise<SuccessResponse>`
|
|
240
|
+
|
|
241
|
+
Scope: `threads_manage_replies`. Endpoint: `POST /{reply-id}/manage_reply` with `hide`.
|
|
242
|
+
|
|
243
|
+
### `replies.listPending(mediaId, options?)` → `Promise<Paginated<ThreadsReply>>`
|
|
244
|
+
|
|
245
|
+
Scope: `threads_manage_replies`. Endpoint: `GET /{media-id}/pending_replies`.
|
|
246
|
+
`options`: `{ fields?, reverse?, approvalStatus?: 'pending' | 'ignored', signal? }`.
|
|
247
|
+
|
|
248
|
+
### `replies.managePending(replyId, approve, signal?)` → `Promise<SuccessResponse>`
|
|
249
|
+
|
|
250
|
+
Scope: `threads_manage_replies`. Endpoint: `POST /{reply-id}/manage_pending_reply`
|
|
251
|
+
with `approve`.
|
|
252
|
+
|
|
253
|
+
```ts
|
|
254
|
+
const replies = await threads.replies.list('MEDIA_ID')
|
|
255
|
+
await threads.replies.publish('MEDIA_ID', 'Thanks!')
|
|
256
|
+
await threads.replies.hide('REPLY_ID')
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
---
|
|
260
|
+
|
|
261
|
+
## Resource: insights
|
|
262
|
+
|
|
263
|
+
### `insights.media(mediaId, metrics?, signal?)` → `Promise<InsightsResponse>`
|
|
264
|
+
|
|
265
|
+
Scope: `threads_manage_insights`. Endpoint: `GET /{media-id}/insights`.
|
|
266
|
+
`metrics` defaults to `['views','likes','replies','reposts','quotes','shares']`.
|
|
267
|
+
|
|
268
|
+
`MediaMetric` = `views | likes | replies | reposts | quotes | shares`.
|
|
269
|
+
|
|
270
|
+
### `insights.user(options?)` → `Promise<InsightsResponse>`
|
|
271
|
+
|
|
272
|
+
Scope: `threads_manage_insights`. Endpoint: `GET /{user}/threads_insights`.
|
|
273
|
+
|
|
274
|
+
| Option | Type | Notes |
|
|
275
|
+
|---|---|---|
|
|
276
|
+
| `userId` | `string` | defaults to configured user / `me` |
|
|
277
|
+
| `metrics` | `UserMetric[]` | defaults to `['views','followers_count']` |
|
|
278
|
+
| `since` / `until` | `number \| Date` | range |
|
|
279
|
+
| `breakdown` | `'country' \| 'city' \| 'age' \| 'gender'` | **required** by the API for `follower_demographics` |
|
|
280
|
+
| `signal` | `AbortSignal` | — |
|
|
281
|
+
|
|
282
|
+
`UserMetric` = `views | likes | replies | reposts | quotes | clicks | followers_count | follower_demographics`.
|
|
283
|
+
|
|
284
|
+
```ts
|
|
285
|
+
await threads.insights.media('MEDIA_ID')
|
|
286
|
+
await threads.insights.user({ metrics: ['follower_demographics'], breakdown: 'country' })
|
|
287
|
+
```
|
|
288
|
+
|
|
289
|
+
---
|
|
290
|
+
|
|
291
|
+
## Resource: mentions
|
|
292
|
+
|
|
293
|
+
### `mentions.list(options?)` → `Promise<Paginated<ThreadsMedia>>`
|
|
294
|
+
|
|
295
|
+
Scope: mentions permission (+ `threads_basic`). Endpoint: `GET /{user}/mentions`.
|
|
296
|
+
`options`: `{ userId?, fields?, since?, until?, limit?, before?, after?, signal? }`.
|
|
297
|
+
|
|
298
|
+
---
|
|
299
|
+
|
|
300
|
+
## Resource: search
|
|
301
|
+
|
|
302
|
+
### `search.keyword(query, options?)` → `Promise<Paginated<ThreadsMedia>>`
|
|
303
|
+
|
|
304
|
+
Scope: `threads_keyword_search` (+ `threads_basic`). Endpoint: `GET /keyword_search`.
|
|
305
|
+
The `owner` field is never returned for search results.
|
|
306
|
+
|
|
307
|
+
| Option | Type | Notes |
|
|
308
|
+
|---|---|---|
|
|
309
|
+
| `searchType` | `'TOP' \| 'RECENT'` | `TOP` = relevance (default), `RECENT` = chronological |
|
|
310
|
+
| `searchMode` | `'KEYWORD' \| 'TAG'` | default `KEYWORD` |
|
|
311
|
+
| `mediaType` | `'TEXT' \| 'IMAGE' \| 'VIDEO'` | restrict media type |
|
|
312
|
+
| `since` / `until` | `number \| Date` | range |
|
|
313
|
+
| `limit` | `number` | default 25, max 100 |
|
|
314
|
+
| `authorUsername` | `string` | exact author filter |
|
|
315
|
+
| `fields` | `string[]` | response fields |
|
|
316
|
+
| `signal` | `AbortSignal` | — |
|
|
317
|
+
|
|
318
|
+
```ts
|
|
319
|
+
const found = await threads.search.keyword('cold brew', { searchType: 'RECENT', limit: 50 })
|
|
320
|
+
```
|
|
321
|
+
|
|
322
|
+
---
|
|
323
|
+
|
|
324
|
+
## OAuth helpers
|
|
325
|
+
|
|
326
|
+
Standalone, tree-shakeable functions. **Token-exchange calls require the app
|
|
327
|
+
secret — run them server-side only.**
|
|
328
|
+
|
|
329
|
+
### `getAuthorizationUrl(options)` → `string`
|
|
330
|
+
|
|
331
|
+
| Option | Type | Notes |
|
|
332
|
+
|---|---|---|
|
|
333
|
+
| `clientId` | `string` | Threads app id |
|
|
334
|
+
| `redirectUri` | `string` | must match app config exactly |
|
|
335
|
+
| `scopes` | `ThreadsScope[]` | non-empty; include `threads_basic` |
|
|
336
|
+
| `state` | `string` | CSRF token (recommended) |
|
|
337
|
+
| `baseUrl` | `string` | defaults to `https://threads.net` |
|
|
338
|
+
|
|
339
|
+
Throws `ThreadsValidationError` if `scopes` is empty.
|
|
340
|
+
|
|
341
|
+
### `exchangeCodeForToken(options)` → `Promise<ShortLivedTokenResponse>`
|
|
342
|
+
|
|
343
|
+
`{ clientId, clientSecret, code, redirectUri, fetch?, baseUrl?, timeoutMs?, logger? }`
|
|
344
|
+
→ `{ access_token, user_id }`. Endpoint: `POST /oauth/access_token`. No retry
|
|
345
|
+
(authorization codes are single-use).
|
|
346
|
+
|
|
347
|
+
### `exchangeForLongLivedToken(options)` → `Promise<LongLivedTokenResponse>`
|
|
348
|
+
|
|
349
|
+
`{ clientSecret, shortLivedToken, ...base }` → `{ access_token, token_type, expires_in }`
|
|
350
|
+
(~60 days). Endpoint: `GET /access_token?grant_type=th_exchange_token`.
|
|
351
|
+
|
|
352
|
+
### `refreshLongLivedToken(options)` → `Promise<LongLivedTokenResponse>`
|
|
353
|
+
|
|
354
|
+
`{ longLivedToken, ...base }` → `{ access_token, token_type, expires_in }`.
|
|
355
|
+
Endpoint: `GET /refresh_access_token?grant_type=th_refresh_token`. Token must be
|
|
356
|
+
≥24h old and unexpired; tokens not refreshed within 60 days expire permanently.
|
|
357
|
+
|
|
358
|
+
---
|
|
359
|
+
|
|
360
|
+
## Errors
|
|
361
|
+
|
|
362
|
+
Every failure is a `ThreadsError`. Narrow with `instanceof`.
|
|
363
|
+
|
|
364
|
+
```text
|
|
365
|
+
ThreadsError
|
|
366
|
+
├─ ThreadsValidationError bad input — no request sent
|
|
367
|
+
├─ ThreadsTimeoutError exceeded timeoutMs
|
|
368
|
+
├─ ThreadsNetworkError fetch failed before a response
|
|
369
|
+
└─ ThreadsAPIError non-2xx response (status, code, subcode, type, fbtraceId)
|
|
370
|
+
├─ ThreadsAuthError 401 / code 190
|
|
371
|
+
└─ ThreadsRateLimitError 429 / throttle code (retryAfterMs?)
|
|
372
|
+
```
|
|
373
|
+
|
|
374
|
+
`ThreadsAPIError` properties: `status?`, `code?`, `subcode?`, `type?`, `fbtraceId?`
|
|
375
|
+
(quote `fbtraceId` when contacting Meta support). `ThreadsRateLimitError` adds
|
|
376
|
+
`retryAfterMs?`.
|
|
377
|
+
|
|
378
|
+
### Id validation (`ThreadsValidationError`)
|
|
379
|
+
|
|
380
|
+
Every id you pass (`mediaId`, `postId`, `replyId`, `userId`, `containerId`, ...)
|
|
381
|
+
is interpolated directly into the request path. The SDK rejects ids
|
|
382
|
+
containing `?`, `#`, or whitespace with `ThreadsValidationError` **before**
|
|
383
|
+
sending anything — otherwise a value like `"123?access_token=..."` could
|
|
384
|
+
smuggle extra query params or redirect the request to an unintended path.
|
|
385
|
+
This matters most if an id comes from untrusted input, e.g. a webhook
|
|
386
|
+
payload — validate/trust ids from external sources before passing them in.
|
|
387
|
+
|
|
388
|
+
### Utilities
|
|
389
|
+
|
|
390
|
+
- `toApiError(status, body, headers?)` → maps a Graph response to the right subtype.
|
|
391
|
+
- `parseRetryAfterMs(value)` → parses a `Retry-After` header (seconds or HTTP-date) to ms.
|
|
392
|
+
|
|
393
|
+
---
|
|
394
|
+
|
|
395
|
+
## Logging
|
|
396
|
+
|
|
397
|
+
```ts
|
|
398
|
+
interface Logger {
|
|
399
|
+
log(level: 'debug' | 'info' | 'warn' | 'error', message: string, context?: LogContext): void
|
|
400
|
+
}
|
|
401
|
+
```
|
|
402
|
+
|
|
403
|
+
The SDK emits `threads.request.success` (debug) and `threads.request.failure`
|
|
404
|
+
(warn/error). **Tokens, app secrets, and OAuth codes are always redacted.**
|
|
405
|
+
|
|
406
|
+
- `noopLogger` — discards everything (default).
|
|
407
|
+
- `redactUrl(url)` — masks `access_token` / `client_secret` / `code` in a URL.
|
|
408
|
+
- `redactParams(params)` — masks the same keys in an object.
|
|
409
|
+
|
|
410
|
+
---
|
|
411
|
+
|
|
412
|
+
## Constants & scopes
|
|
413
|
+
|
|
414
|
+
| Export | Value |
|
|
415
|
+
|---|---|
|
|
416
|
+
| `DEFAULT_BASE_URL` | `https://graph.threads.net` |
|
|
417
|
+
| `AUTHORIZATION_BASE_URL` | `https://threads.net` |
|
|
418
|
+
| `DEFAULT_API_VERSION` | `v1.0` |
|
|
419
|
+
| `THREADS_SCOPES` | array of the 8 documented scopes |
|
|
420
|
+
| `ThreadsScope` | union of known scopes \| `(string & {})` |
|
|
421
|
+
|
|
422
|
+
Documented scopes: `threads_basic`, `threads_content_publish`,
|
|
423
|
+
`threads_read_replies`, `threads_manage_replies`, `threads_manage_insights`,
|
|
424
|
+
`threads_keyword_search`, `threads_delete`, `threads_location_tagging`.
|
|
425
|
+
|
|
426
|
+
---
|
|
427
|
+
|
|
428
|
+
## Types
|
|
429
|
+
|
|
430
|
+
Object types model the API response shape. Fields are optional because the API
|
|
431
|
+
only returns the fields you request via `fields=`.
|
|
432
|
+
|
|
433
|
+
| Type | Purpose |
|
|
434
|
+
|---|---|
|
|
435
|
+
| `ThreadsProfile` | user profile |
|
|
436
|
+
| `ThreadsMedia` | a post |
|
|
437
|
+
| `ThreadsReply` | a reply (adds `has_replies`, `root_post`, `replied_to`, `is_reply`, `hide_status`) |
|
|
438
|
+
| `Paginated<T>` | `{ data: T[]; paging?: { cursors?, next?, previous? } }` |
|
|
439
|
+
| `InsightMetric` / `InsightsResponse` | metric values (`total_value` or `values[]`) |
|
|
440
|
+
| `PublishingLimit` / `QuotaBucket` | quota usage/config |
|
|
441
|
+
| `ContainerRef` | `{ id: string }` |
|
|
442
|
+
| `ContainerStatusResult` | `{ id, status, error_message? }` |
|
|
443
|
+
| `SuccessResponse` | `{ success: boolean }` |
|
|
444
|
+
| `MediaContainerType` | publish input: `TEXT \| IMAGE \| VIDEO \| CAROUSEL` |
|
|
445
|
+
| `MediaType` | read output: `TEXT_POST \| IMAGE \| VIDEO \| CAROUSEL_ALBUM \| AUDIO \| REPOST_FACADE` |
|
|
446
|
+
| `ReplyControl` | `everyone \| accounts_you_follow \| mentioned_only \| parent_post_author_only \| followers_only` |
|
|
447
|
+
| `ContainerStatus` | `EXPIRED \| ERROR \| FINISHED \| IN_PROGRESS \| PUBLISHED` |
|
|
448
|
+
|
|
449
|
+
Option types are also exported: `GetProfileOptions`, `ListPostsOptions`,
|
|
450
|
+
`GetPostOptions`, `CreateContainerInput`, `WaitOptions`, `ListRepliesOptions`,
|
|
451
|
+
`ListPendingRepliesOptions`, `UserInsightsOptions`, `ListMentionsOptions`,
|
|
452
|
+
`KeywordSearchOptions`, plus `MediaMetric`, `UserMetric`, `DemographicBreakdown`.
|