@crosspost/sdk
SDK for interacting with the Crosspost Proxy Service.
This package is designed to be used with
near-sign-verify for authenticating requests via
a wallet or keypair.
Installation
bun install @crosspost/sdk
Usage
import * as near from "fastintear";
import { sign } from "near-sign-verify";
import {
CrosspostClient,
CrosspostError,
isAuthError,
isPlatformError,
isRateLimitError,
isValidationError
} from '@crosspost/sdk';
import type {
ConnectedAccount,
CreatePostRequest,
} from "@crosspost/sdk";
const client = new CrosspostClient({
baseUrl: 'https://your-self-hosted-crosspost-api.com',
});
const authToken = await sign({ signer: near, recipient: "crosspost.near", message: "createPost" });
client.setAuthentication(authToken);
client.setAccountHeader("signer.near")
const connectedAccounts: ApiResponse<ConnectedAccountsResponse> = await client.auth.getConnectedAccounts():
try {
const response = await await client.post.createPost({
targets: [
{
userId: connectedAccounts[0].userId,
platform: connectedAccounts[0].platform
}
],
content: [{
text: "hello world",
media: {
data: imageBlob,
mimeType: 'image/jpeg',
altText: 'a beautiful sunset',
}
}]
} as CreatePostRequest);
console.log('Post created successfully');
console.log('Post ID:', response.id);
console.log('Platform:', response.platform);
console.log('URL:', response.url);
console.log('Created at:', response.createdAt);
} catch (error) {
if (isAuthError(error)) {
console.error('Authentication required. Attempting to authorize...');
const authorized = await client.auth.authorizeNearAccount();
if (authorized) {
return createPost();
}
} else {
console.error('Error creating post:', error);
if (error instanceof CrosspostError) {
if (isPlatformError(error)) {
console.error('Platform:', error.platform);
console.error('Error code:', error.code);
console.error('Details:', error.details);
} else if (isRateLimitError(error)) {
console.error('Rate limited until:', error.details?.rateLimit?.reset);
} else if (isValidationError(error)) {
console.error('Validation errors:', error.details?.validationErrors);
}
if (error.recoverable) {
console.log('This error is recoverable - retry may succeed');
}
} else if (error instanceof Error) {
console.error('Unexpected error:', error.message);
}
}
}
Methods
client.setAuthentication(authToken: string): Promise<void>
- Sets authentication data, necessary
for non-GET requests
client.isAuthenticated(): boolean
- Checks if client is authenticated
client.setAccountHeader(accountId: string): Promise<void>
- Sets X-Near-Account Header,
necessary for GET requests
client.clear(): boolean
- Clears authentication and account header
Auth API (client.auth)
client.auth.authorizeNearAccount(): Promise<ApiResponse<NearAuthorizationResponse>>
- Authorizes
NEAR account
client.auth.unauthorizeNearAccount(): Promise<ApiResponse<NearAuthorizationResponse>>
-
Unauthorizes NEAR account
client.auth.getNearAuthorizationStatus(): Promise<ApiResponse<NearAuthorizationResponse>>
-
Checks authorization status for authenticated account
client.auth.loginToPlatform(platform, options?): Promise<AuthCallbackResponse | ApiResponse<AuthUrlResponse>>
-
Opens popup to initiate OAuth flow with platform
client.auth.refreshToken(platform): Promise<ApiResponse<AuthCallbackResponse>>
- Refreshes
platform token
client.auth.refreshProfile(platform): Promise<ApiResponse<ConnectedAccount>>
- Refreshes user
profile
client.auth.getAuthStatus(platform): Promise<ApiResponse<AuthStatusResponse>>
- Gets
authentication status
client.auth.revokeAuth(platform): Promise<ApiResponse<AuthRevokeResponse>>
- Revokes platform
access
client.auth.getConnectedAccounts(): Promise<ApiResponse<ConnectedAccountsResponse>>
- Lists
connected accounts
Post API (client.post)
Each post operation accepts a request object that includes:
targets
: Array of { platform: string, userId: string }
specifying where to perform the action
- Additional parameters specific to each operation
Available methods:
client.post.createPost(request: CreatePostRequest): Promise<CreatePostResponse>
- Creates posts
on specified platforms
client.post.repost(request: RepostRequest): Promise<RepostResponse>
- Reposts an existing post
client.post.quotePost(request: QuotePostRequest): Promise<QuotePostResponse>
- Quotes an
existing post
client.post.replyToPost(request: ReplyToPostRequest): Promise<ReplyToPostResponse>
- Replies to
a post
client.post.likePost(request: LikePostRequest): Promise<LikePostResponse>
- Likes a post
client.post.unlikePost(request: UnlikePostRequest): Promise<UnlikePostResponse>
- Unlikes a post
client.post.deletePost(request: DeletePostRequest): Promise<DeletePostResponse>
- Deletes posts
Activity API (client.activity)
client.activity.getLeaderboard(options): Promise<LeaderboardResponse>
- Gets activity
leaderboard
client.activity.getAccountActivity(signerId, options): Promise<AccountActivityResponse>
- Gets
account activity
client.activity.getAccountPosts(signerId, options): Promise<AccountPostsResponse>
- Gets account
posts
System API (client.system)
client.system.getRateLimits(): Promise<RateLimitsResponse>
- Gets all rate limits
client.system.getEndpointRateLimit(endpoint): Promise<EndpointRateLimitResponse>
- Gets endpoint
rate limit
client.system.getHealthStatus(): Promise<HealthStatusResponse>
- Gets API health status
API Reference
The SDK supports offset-based pagination for endpoints that return large collections:
const response = await client.activity.getLeaderboard({
limit: 10,
offset: 20,
});
console.log(`Total items: ${response.meta.pagination?.total}`);
console.log(`Current page size: ${response.meta.pagination?.limit}`);
console.log(`Current offset: ${response.meta.pagination?.offset}`);
Multi-Status Responses
Post operations always return multi-status responses:
const response = await client.post.createPost({
targets: [
{ platform: 'twitter', userId: 'user1' },
{ platform: 'facebook', userId: 'user2' },
],
content: [{ text: 'hello world' }],
});
console.log(`Total operations: ${response.data.summary.total}`);
console.log(`Successful: ${response.data.summary.succeeded}`);
console.log(`Failed: ${response.data.summary.failed}`);
response.data.results.forEach((result) => {
console.log(`Success on ${result.platform}: ${result.details.id}`);
});
if (response.data.errors && response.data.errors.length > 0) {
response.data.errors.forEach((error) => {
console.log(`Error on ${error.details.platform}: ${error.message}`);
console.log(`Error code: ${error.code}`);
console.log(`Recoverable: ${error.recoverable}`);
});
}
If all operations fail, the SDK throws a CrosspostError
with the same error structure in
details.errors
:
try {
await client.post.createPost({...});
} catch (error) {
if (error instanceof CrosspostError) {
error.details.errors.forEach(err => {
console.log(`Error on ${err.details.platform}: ${err.message}`);
console.log(`Error code: ${err.code}`);
console.log(`Recoverable: ${err.recoverable}`);
});
}
}
This consistent error structure allows you to use the same error handling logic regardless of
whether you're dealing with partial failures in a multi-status response or complete failure.
Validation Error Handling
The SDK provides detailed validation error information:
try {
await client.post.createPost({
});
} catch (error) {
if (isValidationError(error)) {
console.error('Validation failed:');
if (error.details?.validationErrors) {
Object.entries(error.details.validationErrors).forEach(([field, issues]) => {
console.error(`Field '${field}': ${issues.join(', ')}`);
});
}
}
}
Error Handling Utilities
import {
getErrorDetails,
getErrorMessage,
isAuthError,
isContentError,
isMediaError,
isNetworkError,
isPlatformError,
isPostError,
isRateLimitError,
isRecoverableError,
isValidationError,
} from '@crosspost/sdk';
if (isAuthError(error)) {
}
const message = getErrorMessage(error, 'Default message');
const details = getErrorDetails(error);
Usage Examples
Creating a Post
const textPostResponse = await client.post.createPost({
targets: [{
platform: 'twitter',
userId: 'your-twitter-id',
}],
content: [{
text: 'Hello from Crosspost SDK!',
}],
});
const mediaPostResponse = await client.post.createPost({
targets: [
{ platform: 'twitter', userId: 'your-twitter-id' },
{ platform: 'facebook', userId: 'your-facebook-id' },
],
content: [{
text: 'Check out this image!',
media: [{
data: imageBlob,
mimeType: 'image/jpeg',
altText: 'A beautiful sunset',
}],
}],
});
Post Interactions
await client.post.likePost({
targets: [{
platform: 'twitter',
userId: 'your-twitter-id',
}],
platform: 'twitter',
postId: '1234567890',
});
await client.post.repost({
targets: [
{ platform: 'twitter', userId: 'your-twitter-id' },
{ platform: 'facebook', userId: 'your-facebook-id' },
],
platform: 'twitter',
postId: '1234567890',
});
await client.post.replyToPost({
targets: [{
platform: 'twitter',
userId: 'your-twitter-id',
}],
platform: 'twitter',
postId: '1234567890',
content: [{
text: 'This is a reply',
}],
});
await client.post.deletePost({
targets: [{
platform: 'twitter',
userId: 'your-twitter-id',
}],
posts: [{
platform: 'twitter',
userId: 'your-twitter-id',
postId: '1234567890',
}],
});
Getting Activity Data
const leaderboard = await client.activity.getLeaderboard({
timeframe: 'week',
limit: 10,
});
const activity = await client.activity.getAccountActivity('user.near', {
timeframe: 'month',
});
const posts = await client.activity.getAccountPosts('user.near', {
limit: 20,
offset: 0,
});
Checking Rate Limits
const rateLimits = await client.system.getRateLimits();
const postRateLimit = await client.system.getEndpointRateLimit('post');