Skip to content

Latest commit

 

History

8 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

fopost-java

CI License: MIT

Official Java SDK for the FoPost API. Schedule and publish to +30 social platforms from your code.

<dependency>
  <groupId>com.fopost</groupId>
  <artifactId>fopost-java</artifactId>
  <version>0.1.0</version>
</dependency>
implementation("com.fopost:fopost-java:0.1.0")

Requires Java 17 or newer. HTTP goes through the JDK's own client; the only dependency is Jackson.

0.x release. The public API is still settling and minor versions may contain breaking changes. Pin an exact version if that matters to you.

Quick start

import com.fopost.sdk.FoPost;
import com.fopost.sdk.model.*;
import com.fopost.sdk.param.*;

FoPost client = FoPost.create("fp_...");   // or set FOPOST_API_KEY

Workspace workspace = client.workspaces().list().get(0);
List<Account> accounts = client.accounts().list(workspace.id());

Post post = client.posts().create(
        CreatePostParams.of(workspace.id())
                .content("Hello from Java")
                .accounts(accounts.stream().map(Account::id).toList()));

client.posts().publish(post.id());

A post is one or more content blocks. One block is a plain update; several make a thread:

client.posts().create(CreatePostParams.of(workspace.id())
        .accounts(accountId)
        .content("First post in the thread")
        .block(ContentBlockInput.text("Second one, with an image")
                .media(MediaItem.of("image", "chart.png", "https://.../chart.png"))));

Scheduling

status is draft or scheduled, and a scheduled post needs a time. To send something out now, create it and call publish.

client.posts().create(CreatePostParams.of(workspace.id())
        .accounts(accountId)
        .content("Scheduled with the SDK")
        .schedule(Instant.parse("2026-09-01T10:00:00Z")));

Before publishing, preflight reports the per-account blockers and advisory signals without sending anything, and publish with dryRun rehearses the whole thing:

PreflightResult check = client.posts().preflight(post.id());
if (!check.isReady()) {
    check.accounts().forEach(a -> System.out.println(a.platform() + ": " + a.issues()));
}

Pagination

list returns one page and iterates over its items. autoPaginate walks every page for you, fetching each one as you read it:

Page<Post> page = client.posts().list(
        PostListParams.create().workspaceId(workspace.id()).status(PostStatus.PUBLISHED).perPage(50));
System.out.println(page.meta().total() + " published posts");

for (Post post : client.posts().autoPaginate(PostListParams.create().workspaceId(workspace.id()))) {
    System.out.println(post.id());
}

long failed = client.posts().stream(PostListParams.create().workspaceId(workspace.id()))
        .filter(p -> PostStatus.FAILED.equals(p.status()))
        .count();

Resources

Namespace Methods
posts() list, autoPaginate, stream, get, create, update, delete, duplicate, publish, retry, cancel, preflight, deliveries, publishRuns, analytics, bulkShift, bulkLabel, bulkDelete, validateImport, commitImport, rollbackImport
workspaces() list, get, create, update, delete, analytics
accounts() list, get, create, delete, healthSummary, health, togglePrimary, validate, refreshToken, analytics, communities()
labels() list, get, create, update, delete
webhooks() list, create, update, delete, test
analytics() overview, timeSeries, topPosts, labels, postsTable, postingStreak, demographics, collect
automations() list, get, create, update, delete, toggle, runs, run, trigger, stats
media() list, upload, delete
ai() credits, generateCaption, rewrite, repurposeUrl

accounts().communities() covers the X communities an account can post into: list, sync, search, add, remove.

For an endpoint the SDK does not wrap yet, request sends an authenticated call and hands back the decoded body:

JsonNode body = client.request("GET", "/v1/analytics/overview", null, Map.of("days", 30));

Media

Upload once, then attach the returned file to a content block:

UploadedMedia file = client.media().upload(workspace.id(), Path.of("chart.png")).get(0);

client.posts().create(CreatePostParams.of(workspace.id())
        .accounts(accountId)
        .block(ContentBlockInput.text("Numbers are in").media(file.toMediaItem())));

Webhooks

The signing secret is returned by the create call and never shown again — store it then.

Webhook hook = client.webhooks().create(
        workspace.id(),
        "https://example.com/hooks/fopost",
        List.of(WebhookEvents.POST_PUBLISHED, WebhookEvents.DELIVERY_FAILED));

System.out.println(hook.secret());
client.webhooks().test(hook.id());

AI features

AiCreditBalance balance = client.ai().credits();
System.out.println(balance.creditsRemaining() + " of " + balance.creditsTotal() + " credits left");

CaptionResult caption = client.ai().generateCaption(CaptionParams.create()
        .currentCaption("shipping a new feature")
        .platforms(Platforms.TWITTER, Platforms.LINKEDIN));

API keys reach credits and generateCaption. rewrite and repurposeUrl currently require a signed-in dashboard session and answer 401 to an API key. They are here so the surface is complete once the server opens them up.

Configuration

FoPost client = FoPost.builder()
        .apiKey("fp_...")                          // or FOPOST_API_KEY
        .baseUrl("https://api.fopost.com")         // override for a dev server
        .timeout(Duration.ofSeconds(30))
        .maxRetries(3)                             // total attempts on a 429
        .transport(myTransport)                    // bring your own HTTP stack
        .build();
Env var Used for
FOPOST_API_KEY API key, when none is passed to the builder

Clients are immutable and safe to share across threads. A 429 is retried automatically, waiting for the interval the API asks for in Retry-After (delta-seconds or an HTTP date, capped at 60s). maxRetries counts total attempts, so the default of 3 means two retries.

Error handling

Every non-2xx response throws FoPostException or one of its subclasses, carrying the API's status, code, and message. They are unchecked, so nothing forces a try you did not want.

try {
    client.posts().publish(postId);
} catch (PaymentRequiredException e) {
    System.out.println("Out of credits — upgrade at " + e.upgradeUrl());
} catch (RateLimitException e) {
    System.out.println("Rate limited, retry in " + e.retryAfter());
} catch (FoPostException e) {
    System.out.println("API " + e.status() + " (" + e.code() + "): " + e.getMessage());
}
Status Exception
400, 422 ValidationException
401 AuthenticationException
402 PaymentRequiredException
403 PermissionDeniedException
404 NotFoundException
429 RateLimitException
other FoPostException

A 403 where isSubscriptionRequired() is true means the workspace has no active subscription; read endpoints keep working without one.

Scopes

An API key carries only the scopes granted when it was created, and every request is confined to the workspaces that key can reach. posts also covers publishing, deliveries, and media; the rest are workspaces, accounts, labels, webhooks, analytics, and automations.

Example

examples/CreatePost.java creates a post against a running API.

Contributing

Issues and pull requests are welcome at fopost/fopost-java.

mvn verify

Tests run against a fake transport and never touch the network.

License

MIT

Questions or a problem: fopost.com/contact.

About

Official Java SDK for the FoPost API — schedule and publish social posts, manage accounts, media, and analytics.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages