@eternalsocial/sdk
v0.0.2-beta.5
Published
TypeScript SDK for EternalSocial API
Maintainers
Readme
@eternalsocial/sdk
TypeScript SDK for the Eternal Social Scraper API.
Installation
npm install @eternalsocial/sdk
# or
pnpm add @eternalsocial/sdkUsage
import { createClient } from "@eternalsocial/sdk";
const client = createClient({
apiKey: "your-api-key",
});
// Synchronous request - returns data directly
const profile = await client.instagram.getProfile({ username: "instagram" });
// Async request with webhook - returns immediately
const asyncResult = await client.instagram.getProfile({
username: "instagram",
webhookUrl: "https://your-webhook.com/callback",
});The SDK automatically narrows return types based on whether you provide a webhookUrl:
- Without webhook: Returns response with full data
- With webhook: Returns immediately with processing status
Client API
const client = createClient({ apiKey: "your-api-key" });
// Platform methods
client.instagram.* // getProfile, getFeed, getReels, getPost, getStories, etc.
client.tiktok.* // getProfile, getPosts, getVideo, getVideoComments
client.youtube.* // getChannel, getVideo, getShorts
client.facebook.* // getProfile, getReels, getReel
client.kwai.* // getProfile, getPosts, getPost
// Webhook utilities
client.consumeWebhook(data) // Parse and validate incoming webhook payload
client.VALID_WEBHOOK_TYPES // Array of valid webhook type stringsError Handling
The SDK provides typed errors for different failure scenarios:
import {
createClient,
EternalSocialError,
EternalSocialNetworkError,
EternalSocialAPIError,
EternalSocialValidationError,
} from "@eternalsocial/sdk";
const client = createClient({ apiKey: "your-api-key" });
try {
const reels = await client.instagram.getReels({
username: "instagram",
maxReels: 50
});
} catch (error) {
if (error instanceof EternalSocialValidationError) {
// Invalid request parameters (e.g., maxReels > 100)
console.error(error.message);
// "Validation error on 'maxReels': Too big: expected number to be <=100"
console.error(error.validationErrors);
// [{ path: ['maxReels'], message: '...', code: 'too_big' }]
} else if (error instanceof EternalSocialAPIError) {
// API returned an error
console.error(error.code); // "RATE_LIMITED", "UNAUTHORIZED", etc.
console.error(error.statusCode); // 429, 401, etc.
console.error(error.requestId); // For support tickets
if (error.isRateLimited) {
// Wait and retry
}
if (error.isAuthError) {
// Check API key
}
if (error.isRetryable) {
// Server error (5xx) - safe to retry
}
} else if (error instanceof EternalSocialNetworkError) {
// Network failure (connection, DNS, timeout)
// Always retryable
console.error(error.message);
}
}Error Types
| Error Class | When | Retryable |
|-------------|------|-----------|
| EternalSocialValidationError | Invalid request parameters | No - fix input |
| EternalSocialAPIError | API returned an error | Depends on status |
| EternalSocialNetworkError | Network failure | Yes |
API Error Helpers
EternalSocialAPIError provides helper getters:
| Getter | Status Code | Description |
|--------|-------------|-------------|
| isAuthError | 401 | Invalid API key |
| isForbidden | 403 | Insufficient permissions |
| isNotFound | 404 | Resource not found |
| isRateLimited | 429 | Rate limit exceeded |
| isRetryable | 5xx | Server error, safe to retry |
Examples
// Instagram
const feed = await client.instagram.getFeed({ username: "instagram", maxPosts: 10 });
const reels = await client.instagram.getReels({ username: "instagram", maxReels: 10 });
const post = await client.instagram.getPost({ postUrl: "https://instagram.com/p/ABC123" });
const comments = await client.instagram.getPostComments({ postUrl: "https://instagram.com/p/ABC123" });
const followers = await client.instagram.getFollowers({ username: "instagram", maxFollowers: 100 });
const stories = await client.instagram.getStories({ username: "instagram" });
// TikTok
const tiktokProfile = await client.tiktok.getProfile({ username: "tiktok" });
const posts = await client.tiktok.getPosts({ username: "tiktok", maxPosts: 10 });
const video = await client.tiktok.getVideo({ videoUrl: "https://tiktok.com/@user/video/123" });
// YouTube
const channel = await client.youtube.getChannel({ channelUrl: "https://youtube.com/@MrBeast" });
const shorts = await client.youtube.getShorts({ channelUrl: "https://youtube.com/@MrBeast" });
const video = await client.youtube.getVideo({ videoUrl: "https://youtube.com/watch?v=abc123" });
// Facebook
const fbProfile = await client.facebook.getProfile({ username: "meta" });
const fbReels = await client.facebook.getReels({ username: "meta" });
// Kwai
const kwaiProfile = await client.kwai.getProfile({ username: "kwai" });
const kwaiPosts = await client.kwai.getPosts({ username: "kwai" });Webhooks
For long-running requests, use webhooks to receive results asynchronously.
Tracking Requests
When you make an async request, store the requestId to correlate with the webhook response:
const client = createClient({ apiKey: "your-api-key" });
// Option 1: Store requestId from response
const { requestId } = await client.instagram.getProfile({
username: "instagram",
webhookUrl: "https://your-app.com/webhook",
});
await db.pendingRequests.create({ requestId, username: "instagram" });
// Option 2: Add custom query params to webhook URL
const userId = "user_123";
const jobId = "job_456";
await client.instagram.getProfile({
username: "instagram",
webhookUrl: `https://your-app.com/webhook?userId=${userId}&jobId=${jobId}`,
});Handling Webhooks
import { createClient, EternalSocialValidationError } from "@eternalsocial/sdk";
const client = createClient({ apiKey: "your-api-key" });
app.post("/webhook", async (req, res) => {
// Option 2: Extract custom query params
const { userId, jobId } = req.query;
try {
const payload = client.consumeWebhook(req.body);
// Option 1: Look up original request context by requestId
const context = await db.pendingRequests.findByRequestId(payload.requestId);
if (payload.status === "failed") {
console.error(`Request ${payload.requestId} failed:`, payload.error);
return res.status(200).send("ok");
}
// Narrow by type to get typed data
switch (payload.type) {
case "INSTAGRAM_PROFILE":
// payload.data is now InstagramUserData
console.log(payload.data.username);
console.log(payload.data.full_name);
console.log(payload.data.follower_count);
break;
case "INSTAGRAM_FEED":
case "INSTAGRAM_REELS":
case "INSTAGRAM_HASHTAG":
case "INSTAGRAM_TAGGED":
// payload.data is InstagramMediaItem[]
for (const post of payload.data) {
console.log(post.id, post.like_count, post.caption);
}
break;
case "INSTAGRAM_FOLLOWERS":
case "INSTAGRAM_FOLLOWING":
// payload.data is InstagramUser[]
for (const user of payload.data) {
console.log(user.username, user.full_name);
}
break;
case "TIKTOK_PROFILE":
// payload.data is TikTokUserData
console.log(payload.data.user.uniqueId);
console.log(payload.data.stats.followerCount);
break;
case "TIKTOK_POSTS":
// payload.data is TikTokItem[]
for (const video of payload.data) {
console.log(video.id, video.desc, video.stats.playCount);
}
break;
case "YOUTUBE_CHANNEL_INFO":
// payload.data is YouTubeChannelData
console.log(payload.data.title);
console.log(payload.data.subscriberCount);
break;
// ... handle other types
}
// Common fields available on all successful payloads
console.log(payload.requestId); // Correlate with original request
console.log(payload.itemCount); // Number of items returned
console.log(payload.creditsCharged); // Credits used
res.status(200).send("ok");
} catch (error) {
if (error instanceof EternalSocialValidationError) {
// Invalid webhook payload structure
console.error("Invalid webhook:", error.message);
}
res.status(400).send("invalid payload");
}
});Webhook Payload Types
All successful webhook payloads share these fields:
| Field | Type | Description |
|-------|------|-------------|
| status | "completed" | Always "completed" for success |
| type | string | Request type (e.g., "INSTAGRAM_PROFILE") |
| data | varies | The scraped data (type depends on type) |
| requestId | string | Correlate with your original request |
| itemCount | number? | Number of items returned |
| creditsCharged | number? | Credits charged for this request |
Failed webhook payloads have:
| Field | Type | Description |
|-------|------|-------------|
| status | "failed" | Always "failed" for errors |
| type | string | Which request type failed |
| error | string | Error message |
| requestId | string | Correlate with your original request |
Development
pnpm install
pnpm generate # Regenerate client from OpenAPI
pnpm build # Generate + build (ESM/CJS + types)License
MIT
